diff --git a/.gitattributes b/.gitattributes
index ff4ec94087..3647a7063d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -118,6 +118,7 @@
*.bmp filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
+*.qoi filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index ffacf51e4a..543506197b 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -29,7 +29,6 @@
#### **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/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?**
@@ -37,7 +36,6 @@
#### Code of Conduct
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community.
-For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
And please remember. SixLabors.ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible image processing available to all. Open Source can only exist with your help.
diff --git a/.github/ISSUE_TEMPLATE/commercial-bug-report.yml b/.github/ISSUE_TEMPLATE/commercial-bug-report.yml
deleted file mode 100644
index 6b4d914d7e..0000000000
--- a/.github/ISSUE_TEMPLATE/commercial-bug-report.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-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/oss-bug-report.yml b/.github/ISSUE_TEMPLATE/oss-bug-report.yml
index a4e5619d46..87cd1a7a17 100644
--- a/.github/ISSUE_TEMPLATE/oss-bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/oss-bug-report.yml
@@ -1,5 +1,5 @@
-name: "OSS : Bug Report"
-description: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged.
+name: "Bug Report"
+description: Create a report to help us improve the project. Issues are not guaranteed to be triaged.
labels: ["needs triage"]
body:
- type: checkboxes
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index b5cc5daca2..853cad738b 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -9,6 +9,7 @@ on:
pull_request:
branches:
- main
+ - release/*
types: [ labeled, opened, synchronize, reopened ]
jobs:
Build:
diff --git a/ImageSharp.sln b/ImageSharp.sln
index 3ea3160a79..2967acb8ff 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1
Directory.Build.targets = Directory.Build.targets
LICENSE = LICENSE
README.md = README.md
+ SixLabors.ImageSharp.props = SixLabors.ImageSharp.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}"
@@ -28,7 +29,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{FBE8C1AD-5AEC-4514-9B64-091D8E145865}"
ProjectSection(SolutionItems) = preProject
- .github\ISSUE_TEMPLATE\commercial-bug-report.yml = .github\ISSUE_TEMPLATE\commercial-bug-report.yml
.github\ISSUE_TEMPLATE\config.yml = .github\ISSUE_TEMPLATE\config.yml
.github\ISSUE_TEMPLATE\oss-bug-report.yml = .github\ISSUE_TEMPLATE\oss-bug-report.yml
EndProjectSection
@@ -647,6 +647,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tga", "Tga", "{5DFC394F-136
tests\Images\Input\Tga\targa_8bit_rle.tga = tests\Images\Input\Tga\targa_8bit_rle.tga
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-4935-41CD-BA85-CF11BFF55A45}"
+ ProjectSection(SolutionItems) = preProject
+ tests\Images\Input\Qoi\dice.qoi = tests\Images\Input\Qoi\dice.qoi
+ tests\Images\Input\Qoi\edgecase.qoi = tests\Images\Input\Qoi\edgecase.qoi
+ tests\Images\Input\Qoi\kodim10.qoi = tests\Images\Input\Qoi\kodim10.qoi
+ tests\Images\Input\Qoi\kodim23.qoi = tests\Images\Input\Qoi\kodim23.qoi
+ tests\Images\Input\Qoi\qoi_logo.qoi = tests\Images\Input\Qoi\qoi_logo.qoi
+ tests\Images\Input\Qoi\testcard.qoi = tests\Images\Input\Qoi\testcard.qoi
+ tests\Images\Input\Qoi\testcard_rgba.qoi = tests\Images\Input\Qoi\testcard_rgba.qoi
+ tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -699,6 +711,7 @@ Global
{FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254}
{5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
+ {E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
diff --git a/README.md b/README.md
index 09262eb572..fa51d57cdf 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,8 @@ git submodule update --init --recursive
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.
+Useful tools for development and links to specifications can be found in our wikipage: [Useful-tools-and-links](https://github.com/SixLabors/ImageSharp/wiki/Useful-tools-and-links).
+
## The ImageSharp Team
- [James Jackson-South](https://github.com/jimbobsquarepants)
@@ -109,6 +111,11 @@ Please... Spread the word, contribute algorithms, submit performance improvement
- [Scott Williams](https://github.com/tocsoft)
- [Brian Popow](https://github.com/brianpopow)
+---
+
+
+
-
+ Special thanks to [JetBrains](https://www.jetbrains.com/?from=ImageSharp) for supporting us with open-source licenses for their IDEs.
+
diff --git a/SixLabors.ImageSharp.props b/SixLabors.ImageSharp.props
index 6ec4d8c594..353dce25e7 100644
--- a/SixLabors.ImageSharp.props
+++ b/SixLabors.ImageSharp.props
@@ -2,7 +2,7 @@
-
+
diff --git a/shared-infrastructure b/shared-infrastructure
index 9a6cf00d9a..353b9afe32 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 9a6cf00d9a3d482bb08211dd8309f4724a2735cb
+Subproject commit 353b9afe32a8000410312d17263407cd7bb82d19
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs
index 0eb5952a63..657654a84b 100644
--- a/src/ImageSharp/Advanced/ParallelRowIterator.cs
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs
@@ -50,7 +50,7 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
// Avoid TPL overhead in this trivial case:
@@ -115,7 +115,7 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
@@ -180,7 +180,7 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
// Avoid TPL overhead in this trivial case:
@@ -242,7 +242,7 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
@@ -270,7 +270,7 @@ public static partial class ParallelRowIterator
}
[MethodImpl(InliningOptions.ShortMethod)]
- private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
+ private static int DivideCeil(long dividend, int divisor) => (int)Math.Min(1 + ((dividend - 1) / divisor), int.MaxValue);
private static void ValidateRectangle(Rectangle rectangle)
{
diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs
index 5af5db3cda..aba3c0abdc 100644
--- a/src/ImageSharp/Common/Helpers/Numerics.cs
+++ b/src/ImageSharp/Common/Helpers/Numerics.cs
@@ -73,6 +73,30 @@ internal static class Numerics
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo8(nint x) => x & 7;
+ ///
+ /// Calculates % 64
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Modulo64(int x) => x & 63;
+
+ ///
+ /// Calculates % 64
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint Modulo64(nint x) => x & 63;
+
+ ///
+ /// Calculates % 256
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Modulo256(int x) => x & 255;
+
+ ///
+ /// Calculates % 256
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint Modulo256(nint x) => x & 255;
+
///
/// Fast (x mod m) calculator, with the restriction that
/// should be power of 2.
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index d4d0558238..39fcef9c40 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
@@ -212,6 +213,7 @@ public sealed class Configuration
/// .
/// .
/// .
+ /// .
///
/// The default configuration of .
internal static Configuration CreateDefaultInstance() => new(
@@ -222,5 +224,6 @@ public sealed class Configuration
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new TiffConfigurationModule(),
- new WebpConfigurationModule());
+ new WebpConfigurationModule(),
+ new QoiConfigurationModule());
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index fd23a29e37..ce1660a912 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -668,7 +668,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
if (quantizedPixelRow.Length % 8 != 0)
{
- int startIdx = quantizedPixelRow.Length - 7;
+ int startIdx = quantizedPixelRow.Length - (quantizedPixelRow.Length % 8);
endIdx = quantizedPixelRow.Length;
Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow);
}
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs
index 71458333f0..30f576e5c4 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.cs
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs
@@ -9,9 +9,10 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Formats.Tga;
-using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Formats.Webp;
namespace SixLabors.ImageSharp;
@@ -531,47 +532,47 @@ public static partial class ImageExtensions
cancellationToken);
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The file path to save the image to.
/// Thrown if the path is null.
- public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, default);
+ public static void SaveAsQoi(this Image source, string path) => SaveAsQoi(source, path, default);
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The file path to save the image to.
/// Thrown if the path is null.
/// A representing the asynchronous operation.
- public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, default);
+ public static Task SaveAsQoiAsync(this Image source, string path) => SaveAsQoiAsync(source, path, default);
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The file path to save the image to.
/// The token to monitor for cancellation requests.
/// Thrown if the path is null.
/// A representing the asynchronous operation.
- public static Task SaveAsTgaAsync(this Image source, string path, CancellationToken cancellationToken)
- => SaveAsTgaAsync(source, path, default, cancellationToken);
+ public static Task SaveAsQoiAsync(this Image source, string path, CancellationToken cancellationToken)
+ => SaveAsQoiAsync(source, path, default, cancellationToken);
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The file path to save the image to.
/// The encoder to save the image with.
/// Thrown if the path is null.
- public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) =>
+ public static void SaveAsQoi(this Image source, string path, QoiEncoder encoder) =>
source.Save(
path,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance));
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -579,46 +580,46 @@ public static partial class ImageExtensions
/// The token to monitor for cancellation requests.
/// Thrown if the path is null.
/// A representing the asynchronous operation.
- public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default)
+ public static Task SaveAsQoiAsync(this Image source, string path, QoiEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync(
path,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance),
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance),
cancellationToken);
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The stream to save the image to.
/// Thrown if the stream is null.
- public static void SaveAsTga(this Image source, Stream stream)
- => SaveAsTga(source, stream, default);
+ public static void SaveAsQoi(this Image source, Stream stream)
+ => SaveAsQoi(source, stream, default);
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The stream to save the image to.
/// The token to monitor for cancellation requests.
/// Thrown if the stream is null.
/// A representing the asynchronous operation.
- public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
- => SaveAsTgaAsync(source, stream, default, cancellationToken);
+ public static Task SaveAsQoiAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+ => SaveAsQoiAsync(source, stream, default, cancellationToken);
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The stream to save the image to.
/// The encoder to save the image with.
/// Thrown if the stream is null.
- public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder)
+ public static void SaveAsQoi(this Image source, Stream stream, QoiEncoder encoder)
=> source.Save(
stream,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance));
///
- /// Saves the image to the given stream with the Tga format.
+ /// Saves the image to the given stream with the Qoi format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -626,54 +627,54 @@ public static partial class ImageExtensions
/// The token to monitor for cancellation requests.
/// Thrown if the stream is null.
/// A representing the asynchronous operation.
- public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default)
+ public static Task SaveAsQoiAsync(this Image source, Stream stream, QoiEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync(
stream,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance),
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance),
cancellationToken);
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The file path to save the image to.
/// Thrown if the path is null.
- public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, default);
+ public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, default);
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The file path to save the image to.
/// Thrown if the path is null.
/// A representing the asynchronous operation.
- public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, default);
+ public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, default);
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The file path to save the image to.
/// The token to monitor for cancellation requests.
/// Thrown if the path is null.
/// A representing the asynchronous operation.
- public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken)
- => SaveAsWebpAsync(source, path, default, cancellationToken);
+ public static Task SaveAsTgaAsync(this Image source, string path, CancellationToken cancellationToken)
+ => SaveAsTgaAsync(source, path, default, cancellationToken);
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The file path to save the image to.
/// The encoder to save the image with.
/// Thrown if the path is null.
- public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) =>
+ public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) =>
source.Save(
path,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance));
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The file path to save the image to.
@@ -681,46 +682,46 @@ public static partial class ImageExtensions
/// The token to monitor for cancellation requests.
/// Thrown if the path is null.
/// A representing the asynchronous operation.
- public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default)
+ public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync(
path,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance),
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance),
cancellationToken);
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The stream to save the image to.
/// Thrown if the stream is null.
- public static void SaveAsWebp(this Image source, Stream stream)
- => SaveAsWebp(source, stream, default);
+ public static void SaveAsTga(this Image source, Stream stream)
+ => SaveAsTga(source, stream, default);
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The stream to save the image to.
/// The token to monitor for cancellation requests.
/// Thrown if the stream is null.
/// A representing the asynchronous operation.
- public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
- => SaveAsWebpAsync(source, stream, default, cancellationToken);
+ public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+ => SaveAsTgaAsync(source, stream, default, cancellationToken);
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The stream to save the image to.
/// The encoder to save the image with.
/// Thrown if the stream is null.
- public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder)
+ public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder)
=> source.Save(
stream,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance));
///
- /// Saves the image to the given stream with the Webp format.
+ /// Saves the image to the given stream with the Tga format.
///
/// The image this method extends.
/// The stream to save the image to.
@@ -728,10 +729,10 @@ public static partial class ImageExtensions
/// The token to monitor for cancellation requests.
/// Thrown if the stream is null.
/// A representing the asynchronous operation.
- public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default)
+ public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync(
stream,
- encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance),
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance),
cancellationToken);
///
@@ -836,4 +837,106 @@ public static partial class ImageExtensions
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance),
cancellationToken);
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// Thrown if the path is null.
+ public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, default);
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, default);
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken)
+ => SaveAsWebpAsync(source, path, default, cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The encoder to save the image with.
+ /// Thrown if the path is null.
+ public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) =>
+ source.Save(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance));
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The file path to save the image to.
+ /// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the path is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default)
+ => source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance),
+ cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// Thrown if the stream is null.
+ public static void SaveAsWebp(this Image source, Stream stream)
+ => SaveAsWebp(source, stream, default);
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
+ => SaveAsWebpAsync(source, stream, default, cancellationToken);
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// Thrown if the stream is null.
+ public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder)
+ => source.Save(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance));
+
+ ///
+ /// Saves the image to the given stream with the Webp format.
+ ///
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
+ /// Thrown if the stream is null.
+ /// A representing the asynchronous operation.
+ public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default)
+ => source.SaveAsync(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance),
+ cancellationToken);
+
}
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt
index 64f3bde9cc..538f62d041 100644
--- a/src/ImageSharp/Formats/ImageExtensions.Save.tt
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt
@@ -14,9 +14,10 @@ using SixLabors.ImageSharp.Advanced;
"Jpeg",
"Pbm",
"Png",
+ "Qoi",
"Tga",
- "Webp",
"Tiff",
+ "Webp",
};
foreach (string fmt in formats)
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
index 153dc8a03e..7e25e945a5 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
@@ -71,7 +71,9 @@ internal readonly struct JFifMarker : IEquatable
/// The marker to return.
public static bool TryParse(ReadOnlySpan bytes, out JFifMarker marker)
{
- if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker))
+ // Some images incorrectly use JFXX as the App0 marker (Issue 2478)
+ if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker)
+ || ProfileResolver.IsProfile(bytes, ProfileResolver.JFxxMarker))
{
byte majorVersion = bytes[5];
byte minorVersion = bytes[6];
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
index d80679db69..0877dbc922 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
@@ -212,7 +212,12 @@ internal struct JpegBitReader
private int ReadStream()
{
int value = this.badData ? 0 : this.stream.ReadByte();
- if (value == -1)
+
+ // We've encountered the end of the file stream which means there's no EOI marker or the marker has been read
+ // during decoding of the SOS marker.
+ // When reading individual bits 'badData' simply means we have hit a marker, When data is '0' and the stream is exhausted
+ // we know we have hit the EOI and completed decoding the scan buffer.
+ if (value == -1 || (this.badData && this.data == 0 && this.stream.Position >= this.stream.Length))
{
// We've encountered the end of the file stream which means there's no EOI marker
// in the image or the SOS marker has the wrong dimensions set.
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
index 795f21a57f..c11679feb1 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
@@ -16,6 +16,14 @@ internal static class ProfileResolver
(byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0'
};
+ ///
+ /// Gets the JFXX specific markers.
+ ///
+ public static ReadOnlySpan JFxxMarker => new[]
+ {
+ (byte)'J', (byte)'F', (byte)'X', (byte)'X', (byte)'\0'
+ };
+
///
/// Gets the ICC specific markers.
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
index d6250127f5..5add4c6f06 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
@@ -184,34 +184,39 @@ internal class SpectralConverter : SpectralConverter, IDisposable
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
- // color converter from RGB to TPixel
+ // Color converter from RGB to TPixel
JpegColorConverterBase converter = this.GetColorConverter(this.frame, this.jpegData);
this.colorConverter = converter;
- // resulting image size
+ // Resulting image size
Size pixelSize = CalculateResultingImageSize(this.frame.PixelSize, this.targetSize, out int blockPixelSize);
- // iteration data
+ // Iteration data
int majorBlockWidth = this.frame.Components.Max((component) => component.SizeInBlocks.Width);
int majorVerticalSamplingFactor = this.frame.Components.Max((component) => component.SamplingFactors.Height);
this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize;
- // pixel buffer for resulting image
+ // Pixel buffer for resulting image
this.pixelBuffer = allocator.Allocate2D(
pixelSize.Width,
pixelSize.Height,
this.Configuration.PreferContiguousImageBuffers);
this.paddedProxyPixelRow = allocator.Allocate(pixelSize.Width + 3);
- // component processors from spectral to RGB
+ // Component processors from spectral to RGB
int bufferWidth = majorBlockWidth * blockPixelSize;
- int batchSize = converter.ElementsPerBatch;
- int batchRemainder = bufferWidth & (batchSize - 1);
- Size postProcessorBufferSize = new(bufferWidth + (batchSize - batchRemainder), this.pixelRowsPerStep);
+
+ // Converters process pixels in batches and require target buffer size to be divisible by a batch size
+ // Corner case: image size including jpeg padding is already divisible by a batch size or remainder == 0
+ int elementsPerBatch = converter.ElementsPerBatch;
+ int batchRemainder = bufferWidth & (elementsPerBatch - 1);
+ int widthComplementaryValue = batchRemainder == 0 ? 0 : elementsPerBatch - batchRemainder;
+
+ Size postProcessorBufferSize = new(bufferWidth + widthComplementaryValue, this.pixelRowsPerStep);
this.componentProcessors = this.CreateComponentProcessors(this.frame, this.jpegData, blockPixelSize, postProcessorBufferSize);
- // single 'stride' rgba32 buffer for conversion between spectral and TPixel
+ // Single 'stride' rgba32 buffer for conversion between spectral and TPixel
this.rgbBuffer = allocator.Allocate(pixelSize.Width * 3);
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs
index be27385cd0..c33a8a1968 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs
@@ -5,6 +5,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Memory;
@@ -122,12 +123,26 @@ internal class ComponentProcessor : IDisposable
ref Vector256 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
// Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed
+ DebugGuard.IsTrue(source.Length % 8 == 0, "source must be multiple of 8");
nuint count = source.Vector256Count();
for (nuint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i));
}
}
+ else if (AdvSimd.IsSupported)
+ {
+ ref Vector128 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target));
+ ref Vector128 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
+
+ // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed
+ DebugGuard.IsTrue(source.Length % 8 == 0, "source must be multiple of 8");
+ nuint count = source.Vector128Count();
+ for (nuint i = 0; i < count; i++)
+ {
+ Unsafe.Add(ref targetVectorRef, i) = AdvSimd.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i));
+ }
+ }
else
{
ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target));
@@ -200,13 +215,27 @@ internal class ComponentProcessor : IDisposable
ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target));
// Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed
+ DebugGuard.IsTrue(target.Length % 8 == 0, "target must be multiple of 8");
nuint count = target.Vector256Count();
- var multiplierVector = Vector256.Create(multiplier);
+ Vector256 multiplierVector = Vector256.Create(multiplier);
for (nuint i = 0; i < count; i++)
{
Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector);
}
}
+ else if (AdvSimd.IsSupported)
+ {
+ ref Vector128 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target));
+
+ // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed
+ DebugGuard.IsTrue(target.Length % 8 == 0, "target must be multiple of 8");
+ nuint count = target.Vector128Count();
+ Vector128 multiplierVector = Vector128.Create(multiplier);
+ for (nuint i = 0; i < count; i++)
+ {
+ Unsafe.Add(ref targetVectorRef, i) = AdvSimd.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector);
+ }
+ }
else
{
ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target));
diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
index d49633575d..f629282340 100644
--- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
+++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs
@@ -152,7 +152,6 @@ internal class BinaryDecoder
{
int width = pixels.Width;
int height = pixels.Height;
- int startBit = 0;
MemoryAllocator allocator = configuration.MemoryAllocator;
using IMemoryOwner row = allocator.Allocate(width);
Span rowSpan = row.GetSpan();
@@ -162,23 +161,12 @@ internal class BinaryDecoder
for (int x = 0; x < width;)
{
int raw = stream.ReadByte();
- int bit = startBit;
- startBit = 0;
- for (; bit < 8; bit++)
+ int stopBit = Math.Min(8, width - x);
+ for (int bit = 0; bit < stopBit; bit++)
{
bool bitValue = (raw & (0x80 >> bit)) != 0;
rowSpan[x] = bitValue ? black : white;
x++;
- if (x == width)
- {
- startBit = (bit + 1) & 7; // Round off to below 8.
- if (startBit != 0)
- {
- stream.Seek(-1, System.IO.SeekOrigin.Current);
- }
-
- break;
- }
}
}
diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
index b179c775cf..dddc629b3e 100644
--- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
+++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs
@@ -33,10 +33,14 @@ internal class BinaryEncoder
{
WriteGrayscale(configuration, stream, image);
}
- else
+ else if (componentType == PbmComponentType.Short)
{
WriteWideGrayscale(configuration, stream, image);
}
+ else
+ {
+ throw new ImageFormatException("Component type not supported for Grayscale PBM.");
+ }
}
else if (colorType == PbmColorType.Rgb)
{
@@ -44,14 +48,25 @@ internal class BinaryEncoder
{
WriteRgb(configuration, stream, image);
}
- else
+ else if (componentType == PbmComponentType.Short)
{
WriteWideRgb(configuration, stream, image);
}
+ else
+ {
+ throw new ImageFormatException("Component type not supported for Color PBM.");
+ }
}
else
{
- WriteBlackAndWhite(configuration, stream, image);
+ if (componentType == PbmComponentType.Bit)
+ {
+ WriteBlackAndWhite(configuration, stream, image);
+ }
+ else
+ {
+ throw new ImageFormatException("Component type not supported for Black & White PBM.");
+ }
}
}
@@ -164,8 +179,6 @@ internal class BinaryEncoder
using IMemoryOwner row = allocator.Allocate(width);
Span rowSpan = row.GetSpan();
- int previousValue = 0;
- int startBit = 0;
for (int y = 0; y < height; y++)
{
Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y);
@@ -177,8 +190,9 @@ internal class BinaryEncoder
for (int x = 0; x < width;)
{
- int value = previousValue;
- for (int i = startBit; i < 8; i++)
+ int value = 0;
+ int stopBit = Math.Min(8, width - x);
+ for (int i = 0; i < stopBit; i++)
{
if (rowSpan[x].PackedValue < 128)
{
@@ -186,19 +200,9 @@ internal class BinaryEncoder
}
x++;
- if (x == width)
- {
- previousValue = value;
- startBit = (i + 1) & 7; // Round off to below 8.
- break;
- }
}
- if (startBit == 0)
- {
- stream.WriteByte((byte)value);
- previousValue = 0;
- }
+ stream.WriteByte((byte)value);
}
}
}
diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
index d62ca32807..5d5537e398 100644
--- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
+++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs
@@ -28,7 +28,7 @@ internal static class BufferedReadStreamExtensions
{
innerValue = stream.ReadByte();
}
- while (innerValue != 0x0a);
+ while (innerValue is not 0x0a and not -0x1);
// Continue searching for whitespace.
val = innerValue;
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index fb1d33277a..175a9f777d 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -34,7 +34,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private readonly MemoryAllocator memoryAllocator;
///
- /// The configuration instance for the decoding operation.
+ /// The configuration instance for the encoding operation.
///
private readonly Configuration configuration;
diff --git a/src/ImageSharp/Formats/Qoi/MetadataExtensions.cs b/src/ImageSharp/Formats/Qoi/MetadataExtensions.cs
new file mode 100644
index 0000000000..1e0fa88997
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/MetadataExtensions.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Qoi;
+using SixLabors.ImageSharp.Metadata;
+
+namespace SixLabors.ImageSharp;
+
+///
+/// Extension methods for the type.
+///
+public static partial class MetadataExtensions
+{
+ ///
+ /// Gets the qoi format specific metadata for the image.
+ ///
+ /// The metadata this method extends.
+ /// The .
+ public static QoiMetadata GetQoiMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(QoiFormat.Instance);
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiChannels.cs b/src/ImageSharp/Formats/Qoi/QoiChannels.cs
new file mode 100644
index 0000000000..a76aeef28d
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiChannels.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Provides enumeration of available QOI color channels.
+///
+public enum QoiChannels
+{
+ ///
+ /// Each pixel is an R,G,B triple.
+ ///
+ Rgb = 3,
+
+ ///
+ /// Each pixel is an R,G,B triple, followed by an alpha sample.
+ ///
+ Rgba = 4
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiChunk.cs b/src/ImageSharp/Formats/Qoi/QoiChunk.cs
new file mode 100644
index 0000000000..06886b9691
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiChunk.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Enum that contains the operations that encoder and decoder must process, written
+/// in binary to be easier to compare them in the reference
+///
+internal enum QoiChunk
+{
+ ///
+ /// Indicates that the operation is QOI_OP_RGB where the RGB values are written
+ /// in one byte each one after this marker
+ ///
+ QoiOpRgb = 0b11111110,
+
+ ///
+ /// Indicates that the operation is QOI_OP_RGBA where the RGBA values are written
+ /// in one byte each one after this marker
+ ///
+ QoiOpRgba = 0b11111111,
+
+ ///
+ /// Indicates that the operation is QOI_OP_INDEX where one byte contains a 2-bit
+ /// marker (0b00) followed by an index on the previously seen pixels array 0..63
+ ///
+ QoiOpIndex = 0b00000000,
+
+ ///
+ /// Indicates that the operation is QOI_OP_DIFF where one byte contains a 2-bit
+ /// marker (0b01) followed by 2-bit differences in red, green and blue channel
+ /// with the previous pixel with a bias of 2 (-2..1)
+ ///
+ QoiOpDiff = 0b01000000,
+
+ ///
+ /// Indicates that the operation is QOI_OP_LUMA where one byte contains a 2-bit
+ /// marker (0b01) followed by a 6-bits number that indicates the difference of
+ /// the green channel with the previous pixel. Then another byte that contains
+ /// a 4-bit number that indicates the difference of the red channel minus the
+ /// previous difference, and another 4-bit number that indicates the difference
+ /// of the blue channel minus the green difference
+ /// Example: 0b10[6-bits diff green] 0b[6-bits dr-dg][6-bits db-dg]
+ /// dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
+ /// db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
+ ///
+ QoiOpLuma = 0b10000000,
+
+ ///
+ /// Indicates that the operation is QOI_OP_RUN where one byte contains a 2-bit
+ /// marker (0b11) followed by a 6-bits number that indicates the times that the
+ /// previous pixel is repeated
+ ///
+ QoiOpRun = 0b11000000
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs b/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs
new file mode 100644
index 0000000000..9133f88b91
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+// ReSharper disable InconsistentNaming
+// ReSharper disable IdentifierTypo
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Enum for the different QOI color spaces.
+///
+public enum QoiColorSpace
+{
+ ///
+ /// sRGB color space with linear alpha value
+ ///
+ SrgbWithLinearAlpha,
+
+ ///
+ /// All the values in the color space are linear
+ ///
+ AllChannelsLinear
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs b/src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs
new file mode 100644
index 0000000000..ff40f7e17d
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Registers the image encoders, decoders and mime type detectors for the qoi format.
+///
+public sealed class QoiConfigurationModule : IImageFormatConfigurationModule
+{
+ ///
+ public void Configure(Configuration configuration)
+ {
+ configuration.ImageFormatsManager.SetDecoder(QoiFormat.Instance, QoiDecoder.Instance);
+ configuration.ImageFormatsManager.SetEncoder(QoiFormat.Instance, new QoiEncoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new QoiImageFormatDetector());
+ }
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiConstants.cs b/src/ImageSharp/Formats/Qoi/QoiConstants.cs
new file mode 100644
index 0000000000..9643ccef03
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiConstants.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Text;
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+internal static class QoiConstants
+{
+ private static readonly byte[] SMagic = Encoding.UTF8.GetBytes("qoif");
+
+ ///
+ /// Gets the bytes that indicates the image is QOI
+ ///
+ public static ReadOnlySpan Magic => SMagic;
+
+ ///
+ /// Gets the list of mimetypes that equate to a QOI.
+ /// See https://github.com/phoboslab/qoi/issues/167
+ ///
+ public static string[] MimeTypes { get; } = { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
+
+ ///
+ /// Gets the list of file extensions that equate to a QOI.
+ ///
+ public static string[] FileExtensions { get; } = { "qoi" };
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoder.cs b/src/ImageSharp/Formats/Qoi/QoiDecoder.cs
new file mode 100644
index 0000000000..a54095dfc6
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiDecoder.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+internal class QoiDecoder : ImageDecoder
+{
+ private QoiDecoder()
+ {
+ }
+
+ public static QoiDecoder Instance { get; } = new();
+
+ ///
+ protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
+ {
+ Guard.NotNull(options, nameof(options));
+ Guard.NotNull(stream, nameof(stream));
+
+ QoiDecoderCore decoder = new(options);
+ Image image = decoder.Decode(options.Configuration, stream, cancellationToken);
+
+ ScaleToTargetSize(options, image);
+
+ return image;
+ }
+
+ ///
+ protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
+ {
+ Guard.NotNull(options, nameof(options));
+ Guard.NotNull(stream, nameof(stream));
+ return this.Decode(options, stream, cancellationToken);
+ }
+
+ protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
+ {
+ Guard.NotNull(options, nameof(options));
+ Guard.NotNull(stream, nameof(stream));
+ return new QoiDecoderCore(options).Identify(options.Configuration, stream, cancellationToken);
+ }
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
new file mode 100644
index 0000000000..deb0a37f05
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
@@ -0,0 +1,291 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.IO;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+internal class QoiDecoderCore : IImageDecoderInternals
+{
+ ///
+ /// The global configuration.
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Used the manage memory allocations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The QOI header.
+ ///
+ private QoiHeader header;
+
+ public QoiDecoderCore(DecoderOptions options)
+ {
+ this.Options = options;
+ this.configuration = options.Configuration;
+ this.memoryAllocator = this.configuration.MemoryAllocator;
+ }
+
+ public DecoderOptions Options { get; }
+
+ public Size Dimensions { get; }
+
+ ///
+ public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ // Process the header to get metadata
+ this.ProcessHeader(stream);
+
+ // Create Image object
+ ImageMetadata metadata = new()
+ {
+ DecodedImageFormat = QoiFormat.Instance,
+ HorizontalResolution = this.header.Width,
+ VerticalResolution = this.header.Height,
+ ResolutionUnits = PixelResolutionUnit.AspectRatio
+ };
+ QoiMetadata qoiMetadata = metadata.GetQoiMetadata();
+ qoiMetadata.Channels = this.header.Channels;
+ qoiMetadata.ColorSpace = this.header.ColorSpace;
+ Image image = new(this.configuration, (int)this.header.Width, (int)this.header.Height, metadata);
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+
+ this.ProcessPixels(stream, pixels);
+
+ return image;
+ }
+
+ ///
+ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
+ {
+ this.ProcessHeader(stream);
+ PixelTypeInfo pixelType = new(8 * (int)this.header.Channels);
+ Size size = new((int)this.header.Width, (int)this.header.Height);
+
+ ImageMetadata metadata = new();
+ QoiMetadata qoiMetadata = metadata.GetQoiMetadata();
+ qoiMetadata.Channels = this.header.Channels;
+ qoiMetadata.ColorSpace = this.header.ColorSpace;
+
+ return new ImageInfo(pixelType, size, metadata);
+ }
+
+ ///
+ /// Processes the 14-byte header to validate the image and save the metadata
+ /// in
+ ///
+ /// The stream where the bytes are being read
+ /// If the stream doesn't store a qoi image
+ private void ProcessHeader(BufferedReadStream stream)
+ {
+ Span magicBytes = stackalloc byte[4];
+ Span widthBytes = stackalloc byte[4];
+ Span heightBytes = stackalloc byte[4];
+
+ // Read magic bytes
+ int read = stream.Read(magicBytes);
+ if (read != 4 || !magicBytes.SequenceEqual(QoiConstants.Magic.ToArray()))
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ // If it's a qoi image, read the rest of properties
+ read = stream.Read(widthBytes);
+ if (read != 4)
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ read = stream.Read(heightBytes);
+ if (read != 4)
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ // These numbers are in Big Endian so we have to reverse them to get the real number
+ uint width = BinaryPrimitives.ReadUInt32BigEndian(widthBytes);
+ uint height = BinaryPrimitives.ReadUInt32BigEndian(heightBytes);
+ if (width == 0 || height == 0)
+ {
+ throw new InvalidImageContentException(
+ $"The image has an invalid size: width = {width}, height = {height}");
+ }
+
+ int channels = stream.ReadByte();
+ if (channels is -1 or (not 3 and not 4))
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ int colorSpace = stream.ReadByte();
+ if (colorSpace is -1 or (not 0 and not 1))
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ this.header = new QoiHeader(width, height, (QoiChannels)channels, (QoiColorSpace)colorSpace);
+ }
+
+ [DoesNotReturn]
+ private static void ThrowInvalidImageContentException()
+ => throw new InvalidImageContentException("The image is not a valid QOI image.");
+
+ private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels)
+ where TPixel : unmanaged, IPixel
+ {
+ using IMemoryOwner previouslySeenPixelsBuffer = this.memoryAllocator.Allocate(64, AllocationOptions.Clean);
+ Span previouslySeenPixels = previouslySeenPixelsBuffer.GetSpan();
+ Rgba32 previousPixel = new(0, 0, 0, 255);
+
+ // We save the pixel to avoid loosing the fully opaque black pixel
+ // See https://github.com/phoboslab/qoi/issues/258
+ int pixelArrayPosition = GetArrayPosition(previousPixel);
+ previouslySeenPixels[pixelArrayPosition] = previousPixel;
+ byte operationByte;
+ Rgba32 readPixel = default;
+ Span pixelBytes = MemoryMarshal.CreateSpan(ref Unsafe.As(ref readPixel), 4);
+ TPixel pixel = default;
+
+ for (int i = 0; i < this.header.Height; i++)
+ {
+ Span row = pixels.DangerousGetRowSpan(i);
+ for (int j = 0; j < row.Length; j++)
+ {
+ operationByte = (byte)stream.ReadByte();
+ switch ((QoiChunk)operationByte)
+ {
+ // Reading one pixel with previous alpha intact
+ case QoiChunk.QoiOpRgb:
+ if (stream.Read(pixelBytes[..3]) < 3)
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ readPixel.A = previousPixel.A;
+ pixel.FromRgba32(readPixel);
+ pixelArrayPosition = GetArrayPosition(readPixel);
+ previouslySeenPixels[pixelArrayPosition] = readPixel;
+ break;
+
+ // Reading one pixel with new alpha
+ case QoiChunk.QoiOpRgba:
+ if (stream.Read(pixelBytes) < 4)
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ pixel.FromRgba32(readPixel);
+ pixelArrayPosition = GetArrayPosition(readPixel);
+ previouslySeenPixels[pixelArrayPosition] = readPixel;
+ break;
+
+ default:
+ switch ((QoiChunk)(operationByte & 0b11000000))
+ {
+ // Getting one pixel from previously seen pixels
+ case QoiChunk.QoiOpIndex:
+ readPixel = previouslySeenPixels[operationByte];
+ pixel.FromRgba32(readPixel);
+ break;
+
+ // Get one pixel from the difference (-2..1) of the previous pixel
+ case QoiChunk.QoiOpDiff:
+ int redDifference = (operationByte & 0b00110000) >> 4;
+ int greenDifference = (operationByte & 0b00001100) >> 2;
+ int blueDifference = operationByte & 0b00000011;
+ readPixel = previousPixel with
+ {
+ R = (byte)Numerics.Modulo256(previousPixel.R + (redDifference - 2)),
+ G = (byte)Numerics.Modulo256(previousPixel.G + (greenDifference - 2)),
+ B = (byte)Numerics.Modulo256(previousPixel.B + (blueDifference - 2))
+ };
+ pixel.FromRgba32(readPixel);
+ pixelArrayPosition = GetArrayPosition(readPixel);
+ previouslySeenPixels[pixelArrayPosition] = readPixel;
+ break;
+
+ // Get green difference in 6 bits and red and blue differences
+ // depending on the green one
+ case QoiChunk.QoiOpLuma:
+ int diffGreen = operationByte & 0b00111111;
+ int currentGreen = Numerics.Modulo256(previousPixel.G + (diffGreen - 32));
+ int nextByte = stream.ReadByte();
+ int diffRedDG = nextByte >> 4;
+ int diffBlueDG = nextByte & 0b00001111;
+ int currentRed = Numerics.Modulo256(diffRedDG - 8 + (diffGreen - 32) + previousPixel.R);
+ int currentBlue = Numerics.Modulo256(diffBlueDG - 8 + (diffGreen - 32) + previousPixel.B);
+ readPixel = previousPixel with { R = (byte)currentRed, B = (byte)currentBlue, G = (byte)currentGreen };
+ pixel.FromRgba32(readPixel);
+ pixelArrayPosition = GetArrayPosition(readPixel);
+ previouslySeenPixels[pixelArrayPosition] = readPixel;
+ break;
+
+ // Repeating the previous pixel 1..63 times
+ case QoiChunk.QoiOpRun:
+ int repetitions = operationByte & 0b00111111;
+ if (repetitions is 62 or 63)
+ {
+ ThrowInvalidImageContentException();
+ }
+
+ readPixel = previousPixel;
+ pixel.FromRgba32(readPixel);
+ for (int k = -1; k < repetitions; k++, j++)
+ {
+ if (j == row.Length)
+ {
+ j = 0;
+ i++;
+ row = pixels.DangerousGetRowSpan(i);
+ }
+
+ row[j] = pixel;
+ }
+
+ j--;
+ continue;
+
+ default:
+ ThrowInvalidImageContentException();
+ return;
+ }
+
+ break;
+ }
+
+ row[j] = pixel;
+ previousPixel = readPixel;
+ }
+ }
+
+ // Check stream end
+ for (int i = 0; i < 7; i++)
+ {
+ if (stream.ReadByte() != 0)
+ {
+ ThrowInvalidImageContentException();
+ }
+ }
+
+ if (stream.ReadByte() != 1)
+ {
+ ThrowInvalidImageContentException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int GetArrayPosition(Rgba32 pixel)
+ => Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11));
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiEncoder.cs b/src/ImageSharp/Formats/Qoi/QoiEncoder.cs
new file mode 100644
index 0000000000..b3769d45cb
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiEncoder.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Advanced;
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Image encoder for writing an image to a stream as a QOI image
+///
+public class QoiEncoder : ImageEncoder
+{
+ ///
+ /// Gets the color channels on the image that can be
+ /// RGB or RGBA. This is purely informative. It doesn't
+ /// change the way data chunks are encoded.
+ ///
+ public QoiChannels? Channels { get; init; }
+
+ ///
+ /// Gets the color space of the image that can be sRGB with
+ /// linear alpha or all channels linear. This is purely
+ /// informative. It doesn't change the way data chunks are encoded.
+ ///
+ public QoiColorSpace? ColorSpace { get; init; }
+
+ ///
+ protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken)
+ {
+ QoiEncoderCore encoder = new(this, image.GetMemoryAllocator(), image.GetConfiguration());
+ encoder.Encode(image, stream, cancellationToken);
+ }
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
new file mode 100644
index 0000000000..40b246faf2
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
@@ -0,0 +1,231 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Image encoder for writing an image to a stream as a QOi image
+///
+internal class QoiEncoderCore : IImageEncoderInternals
+{
+ ///
+ /// The encoder with options
+ ///
+ private readonly QoiEncoder encoder;
+
+ ///
+ /// Used the manage memory allocations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The configuration instance for the encoding operation.
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The encoder with options.
+ /// The to use for buffer allocations.
+ /// The configuration of the Encoder.
+ public QoiEncoderCore(QoiEncoder encoder, MemoryAllocator memoryAllocator, Configuration configuration)
+ {
+ this.encoder = encoder;
+ this.memoryAllocator = memoryAllocator;
+ this.configuration = configuration;
+ }
+
+ ///
+ public void Encode(Image image, Stream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(image, nameof(image));
+ Guard.NotNull(stream, nameof(stream));
+
+ this.WriteHeader(image, stream);
+ this.WritePixels(image, stream);
+ WriteEndOfStream(stream);
+ stream.Flush();
+ }
+
+ private void WriteHeader(Image image, Stream stream)
+ {
+ // Get metadata
+ Span width = stackalloc byte[4];
+ Span height = stackalloc byte[4];
+ BinaryPrimitives.WriteUInt32BigEndian(width, (uint)image.Width);
+ BinaryPrimitives.WriteUInt32BigEndian(height, (uint)image.Height);
+ QoiChannels qoiChannels = this.encoder.Channels ?? QoiChannels.Rgba;
+ QoiColorSpace qoiColorSpace = this.encoder.ColorSpace ?? QoiColorSpace.SrgbWithLinearAlpha;
+
+ // Write header to the stream
+ stream.Write(QoiConstants.Magic);
+ stream.Write(width);
+ stream.Write(height);
+ stream.WriteByte((byte)qoiChannels);
+ stream.WriteByte((byte)qoiColorSpace);
+ }
+
+ private void WritePixels(Image image, Stream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ // Start image encoding
+ using IMemoryOwner previouslySeenPixelsBuffer = this.memoryAllocator.Allocate(64, AllocationOptions.Clean);
+ Span previouslySeenPixels = previouslySeenPixelsBuffer.GetSpan();
+ Rgba32 previousPixel = new(0, 0, 0, 255);
+ Rgba32 currentRgba32 = default;
+ Buffer2D pixels = image.Frames[0].PixelBuffer;
+ using IMemoryOwner rgbaRowBuffer = this.memoryAllocator.Allocate(pixels.Width);
+ Span rgbaRow = rgbaRowBuffer.GetSpan();
+
+ for (int i = 0; i < pixels.Height; i++)
+ {
+ Span row = pixels.DangerousGetRowSpan(i);
+ PixelOperations.Instance.ToRgba32(this.configuration, row, rgbaRow);
+ for (int j = 0; j < row.Length && i < pixels.Height; j++)
+ {
+ // We get the RGBA value from pixels
+ currentRgba32 = rgbaRow[j];
+
+ // First, we check if the current pixel is equal to the previous one
+ // If so, we do a QOI_OP_RUN
+ if (currentRgba32.Equals(previousPixel))
+ {
+ /* It looks like this isn't an error, but this makes possible that
+ * files start with a QOI_OP_RUN if their first pixel is a fully opaque
+ * black. However, the decoder of this project takes that into consideration
+ *
+ * To further details, see https://github.com/phoboslab/qoi/issues/258,
+ * and we should discuss what to do about this approach and
+ * if it's correct
+ */
+ int repetitions = 0;
+ do
+ {
+ repetitions++;
+ j++;
+ if (j == row.Length)
+ {
+ j = 0;
+ i++;
+ if (i == pixels.Height)
+ {
+ break;
+ }
+
+ row = pixels.DangerousGetRowSpan(i);
+ PixelOperations.Instance.ToRgba32(this.configuration, row, rgbaRow);
+ }
+
+ currentRgba32 = rgbaRow[j];
+ }
+ while (currentRgba32.Equals(previousPixel) && repetitions < 62);
+
+ j--;
+ stream.WriteByte((byte)((int)QoiChunk.QoiOpRun | (repetitions - 1)));
+
+ /* If it's a QOI_OP_RUN, we don't overwrite the previous pixel since
+ * it will be taken and compared on the next iteration
+ */
+ continue;
+ }
+
+ // else, we check if it exists in the previously seen pixels
+ // If so, we do a QOI_OP_INDEX
+ int pixelArrayPosition = GetArrayPosition(currentRgba32);
+ if (previouslySeenPixels[pixelArrayPosition].Equals(currentRgba32))
+ {
+ stream.WriteByte((byte)pixelArrayPosition);
+ }
+ else
+ {
+ // else, we check if the difference is less than -2..1
+ // Since it wasn't found on the previously seen pixels, we save it
+ previouslySeenPixels[pixelArrayPosition] = currentRgba32;
+
+ int diffRed = currentRgba32.R - previousPixel.R;
+ int diffGreen = currentRgba32.G - previousPixel.G;
+ int diffBlue = currentRgba32.B - previousPixel.B;
+
+ // If so, we do a QOI_OP_DIFF
+ if (diffRed is >= -2 and <= 1 &&
+ diffGreen is >= -2 and <= 1 &&
+ diffBlue is >= -2 and <= 1 &&
+ currentRgba32.A == previousPixel.A)
+ {
+ // Bottom limit is -2, so we add 2 to make it equal to 0
+ int dr = diffRed + 2;
+ int dg = diffGreen + 2;
+ int db = diffBlue + 2;
+ byte valueToWrite = (byte)((int)QoiChunk.QoiOpDiff | (dr << 4) | (dg << 2) | db);
+ stream.WriteByte(valueToWrite);
+ }
+ else
+ {
+ // else, we check if the green difference is less than -32..31 and the rest -8..7
+ // If so, we do a QOI_OP_LUMA
+ int diffRedGreen = diffRed - diffGreen;
+ int diffBlueGreen = diffBlue - diffGreen;
+ if (diffGreen is >= -32 and <= 31 &&
+ diffRedGreen is >= -8 and <= 7 &&
+ diffBlueGreen is >= -8 and <= 7 &&
+ currentRgba32.A == previousPixel.A)
+ {
+ int dr_dg = diffRedGreen + 8;
+ int db_dg = diffBlueGreen + 8;
+ byte byteToWrite1 = (byte)((int)QoiChunk.QoiOpLuma | (diffGreen + 32));
+ byte byteToWrite2 = (byte)((dr_dg << 4) | db_dg);
+ stream.WriteByte(byteToWrite1);
+ stream.WriteByte(byteToWrite2);
+ }
+ else
+ {
+ // else, we check if the alpha is equal to the previous pixel
+ // If so, we do a QOI_OP_RGB
+ if (currentRgba32.A == previousPixel.A)
+ {
+ stream.WriteByte((byte)QoiChunk.QoiOpRgb);
+ stream.WriteByte(currentRgba32.R);
+ stream.WriteByte(currentRgba32.G);
+ stream.WriteByte(currentRgba32.B);
+ }
+ else
+ {
+ // else, we do a QOI_OP_RGBA
+ stream.WriteByte((byte)QoiChunk.QoiOpRgba);
+ stream.WriteByte(currentRgba32.R);
+ stream.WriteByte(currentRgba32.G);
+ stream.WriteByte(currentRgba32.B);
+ stream.WriteByte(currentRgba32.A);
+ }
+ }
+ }
+ }
+
+ previousPixel = currentRgba32;
+ }
+ }
+ }
+
+ private static void WriteEndOfStream(Stream stream)
+ {
+ // Write bytes to end stream
+ for (int i = 0; i < 7; i++)
+ {
+ stream.WriteByte(0);
+ }
+
+ stream.WriteByte(1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int GetArrayPosition(Rgba32 pixel)
+ => Numerics.Modulo64((pixel.R * 3) + (pixel.G * 5) + (pixel.B * 7) + (pixel.A * 11));
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiFormat.cs b/src/ImageSharp/Formats/Qoi/QoiFormat.cs
new file mode 100644
index 0000000000..ca2d7ae452
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiFormat.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Registers the image encoders, decoders and mime type detectors for the qoi format.
+///
+public sealed class QoiFormat : IImageFormat
+{
+ private QoiFormat()
+ {
+ }
+
+ ///
+ /// Gets the shared instance.
+ ///
+ public static QoiFormat Instance { get; } = new QoiFormat();
+
+ ///
+ public string DefaultMimeType => "image/qoi";
+
+ ///
+ public string Name => "QOI";
+
+ ///
+ public IEnumerable MimeTypes => QoiConstants.MimeTypes;
+
+ ///
+ public IEnumerable FileExtensions => QoiConstants.FileExtensions;
+
+ ///
+ public QoiMetadata CreateDefaultFormatMetadata() => new();
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiHeader.cs b/src/ImageSharp/Formats/Qoi/QoiHeader.cs
new file mode 100644
index 0000000000..951d6701b9
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiHeader.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Text;
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Represents the qoi header chunk.
+///
+internal readonly struct QoiHeader
+{
+ public QoiHeader(uint width, uint height, QoiChannels channels, QoiColorSpace colorSpace)
+ {
+ this.Width = width;
+ this.Height = height;
+ this.Channels = channels;
+ this.ColorSpace = colorSpace;
+ }
+
+ ///
+ /// Gets the magic bytes "qoif"
+ ///
+ public byte[] Magic { get; } = Encoding.UTF8.GetBytes("qoif");
+
+ ///
+ /// Gets the image width in pixels (Big Endian)
+ ///
+ public uint Width { get; }
+
+ ///
+ /// Gets the image height in pixels (Big Endian)
+ ///
+ public uint Height { get; }
+
+ ///
+ /// Gets the color channels of the image. 3 = RGB, 4 = RGBA.
+ ///
+ public QoiChannels Channels { get; }
+
+ ///
+ /// Gets the color space of the image. 0 = sRGB with linear alpha, 1 = All channels linear
+ ///
+ public QoiColorSpace ColorSpace { get; }
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs b/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs
new file mode 100644
index 0000000000..d264ec5bc3
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Detects qoi file headers
+///
+public class QoiImageFormatDetector : IImageFormatDetector
+{
+ ///
+ public int HeaderSize => 14;
+
+ ///
+ public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format)
+ {
+ format = this.IsSupportedFileFormat(header) ? QoiFormat.Instance : null;
+ return format != null;
+ }
+
+ private bool IsSupportedFileFormat(ReadOnlySpan header)
+ => header.Length >= this.HeaderSize && QoiConstants.Magic.SequenceEqual(header[..4]);
+}
diff --git a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
new file mode 100644
index 0000000000..610c6c15b8
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Qoi;
+
+///
+/// Provides Qoi specific metadata information for the image.
+///
+public class QoiMetadata : IDeepCloneable
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public QoiMetadata()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The metadata to create an instance from.
+ private QoiMetadata(QoiMetadata other)
+ {
+ this.Channels = other.Channels;
+ this.ColorSpace = other.ColorSpace;
+ }
+
+ ///
+ /// Gets or sets color channels of the image. 3 = RGB, 4 = RGBA.
+ ///
+ public QoiChannels Channels { get; set; }
+
+ ///
+ /// Gets or sets color space of the image. 0 = sRGB with linear alpha, 1 = All channels linear
+ ///
+ public QoiColorSpace ColorSpace { get; set; }
+
+ ///
+ public IDeepCloneable DeepClone() => new QoiMetadata(this);
+}
diff --git a/src/ImageSharp/Formats/Qoi/qoi-specification.pdf b/src/ImageSharp/Formats/Qoi/qoi-specification.pdf
new file mode 100644
index 0000000000..3ffa4bd615
Binary files /dev/null and b/src/ImageSharp/Formats/Qoi/qoi-specification.pdf differ
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
index 8be0939d3a..ea03094878 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
@@ -125,13 +125,29 @@ internal sealed class T6BitReader : T4BitReader
if (value == Len7Code0000000.Code)
{
this.Code = Len7Code0000000;
- return false;
+
+ // We do not support Extensions1D codes, but some encoders (scanner from epson) write a premature EOL code,
+ // which at this point cannot be distinguished from the marker, because we read the data bit by bit.
+ // Read the next 5 bit, if its a EOL code return true, indicating its the end of the image.
+ if (this.ReadValue(5) == 1)
+ {
+ return true;
+ }
+
+ throw new NotSupportedException("ccitt extensions 1D codes are not supported.");
}
if (value == Len7Code0000001.Code)
{
this.Code = Len7Code0000001;
- return false;
+
+ // Same as above, we do not support Extensions2D codes, but it could be a EOL instead.
+ if (this.ReadValue(5) == 1)
+ {
+ return true;
+ }
+
+ throw new NotSupportedException("ccitt extensions 2D codes are not supported.");
}
if (value == Len7Code0000011.Code)
diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
index 9a4e4ba2b9..1c838b0b76 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
@@ -44,6 +44,7 @@ internal static class HorizontalPredictor
UndoRgb24Bit(pixelBytes, width);
break;
case TiffColorType.Rgba8888:
+ case TiffColorType.Cmyk:
UndoRgba32Bit(pixelBytes, width);
break;
case TiffColorType.Rgb161616:
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index 45bbed12d5..aed6d4ec66 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -344,7 +344,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
ArgumentNullException.ThrowIfNull(valueWidth);
}
- if (!tags.TryGetValue(ExifTag.TileWidth, out IExifValue valueLength))
+ if (!tags.TryGetValue(ExifTag.TileLength, out IExifValue valueLength))
{
ArgumentNullException.ThrowIfNull(valueLength);
}
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
index 1770415062..68bf09f948 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs
@@ -156,7 +156,7 @@ internal class Vp8Residual
return LossyUtils.Vp8BitCost(0, (byte)p0);
}
- if (Avx2.IsSupported)
+ if (Sse2.IsSupported)
{
Span scratch = stackalloc byte[32];
Span ctxs = scratch.Slice(0, 16);
@@ -165,19 +165,23 @@ internal class Vp8Residual
// Precompute clamped levels and contexts, packed to 8b.
ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs);
- Vector256 c0 = Unsafe.As>(ref outputRef).AsInt16();
- Vector256 d0 = Avx2.Subtract(Vector256.Zero, c0);
- Vector256 e0 = Avx2.Max(c0, d0); // abs(v), 16b
- Vector256 f = Avx2.PackSignedSaturate(e0, e0);
- Vector256 g = Avx2.Min(f.AsByte(), Vector256.Create((byte)2));
- Vector256 h = Avx2.Min(f.AsByte(), Vector256.Create((byte)67)); // clampLevel in [0..67]
+ Vector128 c0 = Unsafe.As>(ref outputRef).AsInt16();
+ Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)).AsInt16();
+ Vector128 d0 = Sse2.Subtract(Vector128.Zero, c0);
+ Vector128 d1 = Sse2.Subtract(Vector128.Zero, c1);
+ Vector128 e0 = Sse2.Max(c0, d0); // abs(v), 16b
+ Vector128 e1 = Sse2.Max(c1, d1);
+ Vector128 f = Sse2.PackSignedSaturate(e0, e1);
+ Vector128 g = Sse2.Min(f.AsByte(), Vector128.Create((byte)2)); // context = 0, 1, 2
+ Vector128 h = Sse2.Min(f.AsByte(), Vector128.Create((byte)67)); // clampLevel in [0..67]
ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs);
ref byte levelsRef = ref MemoryMarshal.GetReference(levels);
ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels);
- Unsafe.As>(ref ctxsRef) = g.GetLower();
- Unsafe.As>(ref levelsRef) = h.GetLower();
- Unsafe.As>(ref absLevelsRef) = e0.AsUInt16();
+ Unsafe.As>(ref ctxsRef) = g;
+ Unsafe.As>(ref levelsRef) = h;
+ Unsafe.As>(ref absLevelsRef) = e0.AsUInt16();
+ Unsafe.As>(ref Unsafe.Add(ref absLevelsRef, 8)) = e1.AsUInt16();
int level;
int flevel;
diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs
index 96a9b5ba01..0f5113eff4 100644
--- a/src/ImageSharp/IO/IFileSystem.cs
+++ b/src/ImageSharp/IO/IFileSystem.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.IO;
@@ -9,16 +9,32 @@ namespace SixLabors.ImageSharp.IO;
internal interface IFileSystem
{
///
- /// Returns a readable stream as defined by the path.
+ /// Opens a file as defined by the path and returns it as a readable stream.
///
/// Path to the file to open.
- /// A stream representing the file to open.
+ /// A stream representing the opened file.
Stream OpenRead(string path);
///
- /// Creates or opens a file and returns it as a writable stream as defined by the path.
+ /// Opens a file as defined by the path and returns it as a readable stream
+ /// that can be used for asynchronous reading.
///
/// Path to the file to open.
- /// A stream representing the file to open.
+ /// A stream representing the opened file.
+ Stream OpenReadAsynchronous(string path);
+
+ ///
+ /// Creates or opens a file as defined by the path and returns it as a writable stream.
+ ///
+ /// Path to the file to open.
+ /// A stream representing the opened file.
Stream Create(string path);
+
+ ///
+ /// Creates or opens a file as defined by the path and returns it as a writable stream
+ /// that can be used for asynchronous reading and writing.
+ ///
+ /// Path to the file to open.
+ /// A stream representing the opened file.
+ Stream CreateAsynchronous(string path);
}
diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs
index f4dfa2fe14..d1f619f486 100644
--- a/src/ImageSharp/IO/LocalFileSystem.cs
+++ b/src/ImageSharp/IO/LocalFileSystem.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.IO;
@@ -11,6 +11,24 @@ internal sealed class LocalFileSystem : IFileSystem
///
public Stream OpenRead(string path) => File.OpenRead(path);
+ ///
+ public Stream OpenReadAsynchronous(string path) => File.Open(path, new FileStreamOptions
+ {
+ Mode = FileMode.Open,
+ Access = FileAccess.Read,
+ Share = FileShare.Read,
+ Options = FileOptions.Asynchronous,
+ });
+
///
public Stream Create(string path) => File.Create(path);
+
+ ///
+ public Stream CreateAsynchronous(string path) => File.Open(path, new FileStreamOptions
+ {
+ Mode = FileMode.Create,
+ Access = FileAccess.ReadWrite,
+ Share = FileShare.None,
+ Options = FileOptions.Asynchronous,
+ });
}
diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs
index 24a737493c..a20e5d6c58 100644
--- a/src/ImageSharp/Image.FromFile.cs
+++ b/src/ImageSharp/Image.FromFile.cs
@@ -72,7 +72,7 @@ public abstract partial class Image
{
Guard.NotNull(options, nameof(options));
- using Stream stream = options.Configuration.FileSystem.OpenRead(path);
+ await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path);
return await DetectFormatAsync(options, stream, cancellationToken).ConfigureAwait(false);
}
@@ -81,7 +81,7 @@ public abstract partial class Image
/// A return value indicates whether the operation succeeded.
///
/// The image file to open and to read the header from.
- /// if the information can be read; otherwise,
+ /// The .
/// The path is null.
/// The file stream is not readable or the image format is not supported.
/// The encoded image contains invalid content.
@@ -144,7 +144,7 @@ public abstract partial class Image
CancellationToken cancellationToken = default)
{
Guard.NotNull(options, nameof(options));
- using Stream stream = options.Configuration.FileSystem.OpenRead(path);
+ await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path);
return await IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
}
@@ -214,7 +214,7 @@ public abstract partial class Image
string path,
CancellationToken cancellationToken = default)
{
- using Stream stream = options.Configuration.FileSystem.OpenRead(path);
+ await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path);
return await LoadAsync(options, stream, cancellationToken).ConfigureAwait(false);
}
@@ -291,7 +291,7 @@ public abstract partial class Image
Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path));
- using Stream stream = options.Configuration.FileSystem.OpenRead(path);
+ await using Stream stream = options.Configuration.FileSystem.OpenReadAsynchronous(path);
return await LoadAsync(options, stream, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs
index 1748ca00e0..63f9e64f6c 100644
--- a/src/ImageSharp/Image.FromStream.cs
+++ b/src/ImageSharp/Image.FromStream.cs
@@ -29,7 +29,7 @@ public abstract partial class Image
///
/// The general decoder options.
/// The image stream to read the header from.
- /// if a match is found; otherwise,
+ /// The .
/// The options are null.
/// The stream is null.
/// The stream is not readable or the image format is not supported.
@@ -79,7 +79,7 @@ public abstract partial class Image
/// Reads the raw image information from the specified stream without fully decoding it.
///
/// The image stream to read the header from.
- /// if the information can be read; otherwise,
+ /// The .
/// The stream is null.
/// The stream is not readable or the image format is not supported.
/// The encoded image contains invalid content.
diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs
index cf970b3166..75e4f13257 100644
--- a/src/ImageSharp/ImageExtensions.cs
+++ b/src/ImageSharp/ImageExtensions.cs
@@ -70,7 +70,7 @@ public static partial class ImageExtensions
Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder));
- using Stream fs = source.GetConfiguration().FileSystem.Create(path);
+ await using Stream fs = source.GetConfiguration().FileSystem.CreateAsynchronous(path);
await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
}
diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs
index ed0db9b85b..69654329c4 100644
--- a/src/ImageSharp/Image{TPixel}.cs
+++ b/src/ImageSharp/Image{TPixel}.cs
@@ -418,12 +418,7 @@ public sealed class Image : Image
{
Guard.NotNull(frames, nameof(frames));
- ImageFrame? rootFrame = frames.FirstOrDefault();
-
- if (rootFrame == null)
- {
- throw new ArgumentException("Must not be empty.", nameof(frames));
- }
+ ImageFrame? rootFrame = frames.FirstOrDefault() ?? throw new ArgumentException("Must not be empty.", nameof(frames));
Size rootSize = rootFrame.Size();
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs
index 1cf63eb24c..6bf25717ce 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs
@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats;
///
-/// Packed pixel type containing three 16-bit unsigned normalized values ranging from 0 to 635535.
+/// Packed pixel type containing three 16-bit unsigned normalized values ranging from 0 to 65535.
///
/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form.
///
diff --git a/src/ImageSharp/Primitives/LongRational.cs b/src/ImageSharp/Primitives/LongRational.cs
index e92875a98b..69139ac9c4 100644
--- a/src/ImageSharp/Primitives/LongRational.cs
+++ b/src/ImageSharp/Primitives/LongRational.cs
@@ -131,6 +131,11 @@ internal readonly struct LongRational : IEquatable
/// Whether to use the best possible precision when parsing the value.
public static LongRational FromDouble(double value, bool bestPrecision)
{
+ if (value == 0.0)
+ {
+ return new LongRational(0, 1);
+ }
+
if (double.IsNaN(value))
{
return new LongRational(0, 0);
@@ -201,11 +206,6 @@ internal readonly struct LongRational : IEquatable
return this;
}
- if (this.Numerator == 0)
- {
- return new LongRational(0, 0);
- }
-
if (this.Numerator == this.Denominator)
{
return new LongRational(1, 1);
diff --git a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
index ad93d6f167..25e504831d 100644
--- a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
@@ -15,277 +15,277 @@ public static class DrawImageExtensions
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
+ /// The image to draw on the currently processing image.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
+ Image foreground,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
- return DrawImage(source, image, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
+ return DrawImage(source, foreground, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Rectangle rectangle,
+ Image foreground,
+ Rectangle foregroundRectangle,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
- return DrawImage(source, image, rectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
+ return DrawImage(source, foreground, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
+ /// The image to draw on the currently processing image.
/// The color blending mode.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
+ Image foreground,
PixelColorBlendingMode colorBlending,
float opacity)
- => DrawImage(source, image, Point.Empty, colorBlending, opacity);
+ => DrawImage(source, foreground, Point.Empty, colorBlending, opacity);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The color blending mode.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Rectangle rectangle,
+ Image foreground,
+ Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
float opacity)
- => DrawImage(source, image, rectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
+ => DrawImage(source, foreground, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
+ /// The image to draw on the currently processing image.
/// The color blending mode.
/// The alpha composition mode.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
+ Image foreground,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity)
- => DrawImage(source, image, Point.Empty, colorBlending, alphaComposition, opacity);
+ => DrawImage(source, foreground, Point.Empty, colorBlending, alphaComposition, opacity);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The color blending mode.
/// The alpha composition mode.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Rectangle rectangle,
+ Image foreground,
+ Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity)
- => DrawImage(source, image, Point.Empty, rectangle, colorBlending, alphaComposition, opacity);
+ => DrawImage(source, foreground, Point.Empty, foregroundRectangle, colorBlending, alphaComposition, opacity);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
+ /// The image to draw on the currently processing image.
/// The options, including the blending type and blending amount.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
+ Image foreground,
GraphicsOptions options)
- => DrawImage(source, image, Point.Empty, options);
+ => DrawImage(source, foreground, Point.Empty, options);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The options, including the blending type and blending amount.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Rectangle rectangle,
+ Image foreground,
+ Rectangle foregroundRectangle,
GraphicsOptions options)
- => DrawImage(source, image, Point.Empty, rectangle, options);
+ => DrawImage(source, foreground, Point.Empty, foregroundRectangle, options);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
+ Image foreground,
+ Point backgroundLocation,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
- return DrawImage(source, image, location, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
+ return DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
- Rectangle rectangle,
+ Image foreground,
+ Point backgroundLocation,
+ Rectangle foregroundRectangle,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
- return DrawImage(source, image, location, rectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
+ return DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
/// The color blending to apply.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
+ Image foreground,
+ Point backgroundLocation,
PixelColorBlendingMode colorBlending,
float opacity)
- => DrawImage(source, image, location, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
+ => DrawImage(source, foreground, backgroundLocation, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The color blending to apply.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
- Rectangle rectangle,
+ Image foreground,
+ Point backgroundLocation,
+ Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
float opacity)
- => DrawImage(source, image, location, rectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
+ => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
/// The options containing the blend mode and opacity.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
+ Image foreground,
+ Point backgroundLocation,
GraphicsOptions options)
- => DrawImage(source, image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
+ => DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The options containing the blend mode and opacity.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
- Rectangle rectangle,
+ Image foreground,
+ Point backgroundLocation,
+ Rectangle foregroundRectangle,
GraphicsOptions options)
- => DrawImage(source, image, location, rectangle, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
+ => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
/// The color blending to apply.
/// The alpha composition mode.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
+ Image foreground,
+ Point backgroundLocation,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity)
- => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity));
+ => source.ApplyProcessor(new DrawImageProcessor(foreground, backgroundLocation, foreground.Bounds, colorBlending, alphaComposition, opacity));
///
/// Draws the given image together with the currently processing image by blending their pixels.
///
/// The current image processing context.
- /// The image to draw on the currently processing image.
- /// The location on the currenty processing image at which to draw.
- /// The rectangle structure that specifies the portion of the image to draw.
+ /// The image to draw on the currently processing image.
+ /// The location on the currently processing image at which to draw.
+ /// The rectangle structure that specifies the portion of the image to draw.
/// The color blending to apply.
/// The alpha composition mode.
/// The opacity of the image to draw. Must be between 0 and 1.
/// The .
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
- Image image,
- Point location,
- Rectangle rectangle,
+ Image foreground,
+ Point backgroundLocation,
+ Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity) =>
source.ApplyProcessor(
- new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity),
- rectangle);
+ new DrawImageProcessor(foreground, backgroundLocation, foregroundRectangle, colorBlending, alphaComposition, opacity),
+ foregroundRectangle);
}
diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs
index 88b59b7dc6..6ecf16fc6b 100644
--- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs
@@ -14,20 +14,41 @@ public class DrawImageProcessor : IImageProcessor
///
/// Initializes a new instance of the class.
///
- /// The image to blend.
- /// The location to draw the blended image.
+ /// The image to blend.
+ /// The location to draw the foreground image on the background.
/// The blending mode to use when drawing the image.
/// The Alpha blending mode to use when drawing the image.
/// The opacity of the image to blend.
public DrawImageProcessor(
- Image image,
- Point location,
+ Image foreground,
+ Point backgroundLocation,
PixelColorBlendingMode colorBlendingMode,
PixelAlphaCompositionMode alphaCompositionMode,
float opacity)
+ : this(foreground, backgroundLocation, foreground.Bounds, colorBlendingMode, alphaCompositionMode, opacity)
{
- this.Image = image;
- this.Location = location;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The image to blend.
+ /// The location to draw the foreground image on the background.
+ /// The rectangular portion of the foreground image to draw.
+ /// The blending mode to use when drawing the image.
+ /// The Alpha blending mode to use when drawing the image.
+ /// The opacity of the image to blend.
+ public DrawImageProcessor(
+ Image foreground,
+ Point backgroundLocation,
+ Rectangle foregroundRectangle,
+ PixelColorBlendingMode colorBlendingMode,
+ PixelAlphaCompositionMode alphaCompositionMode,
+ float opacity)
+ {
+ this.ForeGround = foreground;
+ this.BackgroundLocation = backgroundLocation;
+ this.ForegroundRectangle = foregroundRectangle;
this.ColorBlendingMode = colorBlendingMode;
this.AlphaCompositionMode = alphaCompositionMode;
this.Opacity = opacity;
@@ -36,12 +57,17 @@ public class DrawImageProcessor : IImageProcessor
///
/// Gets the image to blend.
///
- public Image Image { get; }
+ public Image ForeGround { get; }
+
+ ///
+ /// Gets the location to draw the foreground image on the background.
+ ///
+ public Point BackgroundLocation { get; }
///
- /// Gets the location to draw the blended image.
+ /// Gets the rectangular portion of the foreground image to draw.
///
- public Point Location { get; }
+ public Rectangle ForegroundRectangle { get; }
///
/// Gets the blending mode to use when drawing the image.
@@ -62,8 +88,8 @@ public class DrawImageProcessor : IImageProcessor
public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
where TPixelBg : unmanaged, IPixel
{
- ProcessorFactoryVisitor visitor = new(configuration, this, source, sourceRectangle);
- this.Image.AcceptVisitor(visitor);
+ ProcessorFactoryVisitor visitor = new(configuration, this, source);
+ this.ForeGround.AcceptVisitor(visitor);
return visitor.Result!;
}
@@ -73,14 +99,15 @@ public class DrawImageProcessor : IImageProcessor
private readonly Configuration configuration;
private readonly DrawImageProcessor definition;
private readonly Image source;
- private readonly Rectangle sourceRectangle;
- public ProcessorFactoryVisitor(Configuration configuration, DrawImageProcessor definition, Image source, Rectangle sourceRectangle)
+ public ProcessorFactoryVisitor(
+ Configuration configuration,
+ DrawImageProcessor definition,
+ Image source)
{
this.configuration = configuration;
this.definition = definition;
this.source = source;
- this.sourceRectangle = sourceRectangle;
}
public IImageProcessor? Result { get; private set; }
@@ -91,8 +118,8 @@ public class DrawImageProcessor : IImageProcessor
this.configuration,
image,
this.source,
- this.sourceRectangle,
- this.definition.Location,
+ this.definition.BackgroundLocation,
+ this.definition.ForegroundRectangle,
this.definition.ColorBlendingMode,
this.definition.AlphaCompositionMode,
this.definition.Opacity);
diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
index 436a447972..378ea20fa8 100644
--- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
+++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
@@ -21,36 +21,42 @@ internal class DrawImageProcessor : ImageProcessor
/// Initializes a new instance of the class.
///
/// The configuration which allows altering default behaviour or extending the library.
- /// The foreground to blend with the currently processing image.
- /// The source for the current processor instance.
- /// The source area to process for the current processor instance.
- /// The location to draw the blended image.
+ /// The foreground to blend with the currently processing image.
+ /// The source for the current processor instance.
+ /// The location to draw the blended image.
+ /// The source area to process for the current processor instance.
/// The blending mode to use when drawing the image.
- /// The Alpha blending mode to use when drawing the image.
+ /// The alpha blending mode to use when drawing the image.
/// The opacity of the image to blend. Must be between 0 and 1.
public DrawImageProcessor(
Configuration configuration,
- Image image,
- Image source,
- Rectangle sourceRectangle,
- Point location,
+ Image foregroundImage,
+ Image backgroundImage,
+ Point backgroundLocation,
+ Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlendingMode,
PixelAlphaCompositionMode alphaCompositionMode,
float opacity)
- : base(configuration, source, sourceRectangle)
+ : base(configuration, backgroundImage, backgroundImage.Bounds)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
- this.Image = image;
+ this.ForegroundImage = foregroundImage;
+ this.ForegroundRectangle = foregroundRectangle;
this.Opacity = opacity;
this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
- this.Location = location;
+ this.BackgroundLocation = backgroundLocation;
}
///
/// Gets the image to blend
///
- public Image Image { get; }
+ public Image ForegroundImage { get; }
+
+ ///
+ /// Gets the rectangular portion of the foreground image to draw.
+ ///
+ public Rectangle ForegroundRectangle { get; }
///
/// Gets the opacity of the image to blend
@@ -65,43 +71,57 @@ internal class DrawImageProcessor : ImageProcessor
///
/// Gets the location to draw the blended image
///
- public Point Location { get; }
+ public Point BackgroundLocation { get; }
///
protected override void OnFrameApply(ImageFrame source)
{
- Rectangle sourceRectangle = this.SourceRectangle;
- Configuration configuration = this.Configuration;
-
- Image targetImage = this.Image;
- PixelBlender blender = this.Blender;
- int locationY = this.Location.Y;
+ // Align the bounds so that both the source and targets are the same width and height for blending.
+ // We ensure that negative locations are subtracted from both bounds so that foreground images can partially overlap.
+ Rectangle foregroundRectangle = this.ForegroundRectangle;
- // Align start/end positions.
- Rectangle bounds = targetImage.Bounds;
+ // Sanitize the location so that we don't try and sample outside the image.
+ int left = this.BackgroundLocation.X;
+ int top = this.BackgroundLocation.Y;
- int minX = Math.Max(this.Location.X, sourceRectangle.X);
- int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right);
- int targetX = minX - this.Location.X;
-
- int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
- int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
-
- int width = maxX - minX;
+ if (this.BackgroundLocation.X < 0)
+ {
+ foregroundRectangle.Width += this.BackgroundLocation.X;
+ left = 0;
+ }
- Rectangle workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+ if (this.BackgroundLocation.Y < 0)
+ {
+ foregroundRectangle.Height += this.BackgroundLocation.Y;
+ top = 0;
+ }
- // Not a valid operation because rectangle does not overlap with this image.
- if (workingRect.Width <= 0 || workingRect.Height <= 0)
+ int width = foregroundRectangle.Width;
+ int height = foregroundRectangle.Height;
+ if (width <= 0 || height <= 0)
{
- throw new ImageProcessingException(
- "Cannot draw image because the source image does not overlap the target image.");
+ // Nothing to do, return.
+ return;
}
- DrawImageProcessor.RowOperation operation = new(source.PixelBuffer, targetImage.Frames.RootFrame.PixelBuffer, blender, configuration, minX, width, locationY, targetX, this.Opacity);
+ // Sanitize the dimensions so that we don't try and sample outside the image.
+ foregroundRectangle = Rectangle.Intersect(foregroundRectangle, this.ForegroundImage.Bounds);
+ Rectangle backgroundRectangle = Rectangle.Intersect(new(left, top, width, height), this.SourceRectangle);
+ Configuration configuration = this.Configuration;
+
+ DrawImageProcessor.RowOperation operation =
+ new(
+ configuration,
+ source.PixelBuffer,
+ this.ForegroundImage.Frames.RootFrame.PixelBuffer,
+ backgroundRectangle,
+ foregroundRectangle,
+ this.Blender,
+ this.Opacity);
+
ParallelRowIterator.IterateRows(
configuration,
- workingRect,
+ new(0, 0, foregroundRectangle.Width, foregroundRectangle.Height),
in operation);
}
@@ -110,36 +130,30 @@ internal class DrawImageProcessor : ImageProcessor
///
private readonly struct RowOperation : IRowOperation
{
- private readonly Buffer2D source;
- private readonly Buffer2D target;
+ private readonly Buffer2D background;
+ private readonly Buffer2D foreground;
private readonly PixelBlender blender;
private readonly Configuration configuration;
- private readonly int minX;
- private readonly int width;
- private readonly int locationY;
- private readonly int targetX;
+ private readonly Rectangle foregroundRectangle;
+ private readonly Rectangle backgroundRectangle;
private readonly float opacity;
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
- Buffer2D source,
- Buffer2D target,
- PixelBlender blender,
Configuration configuration,
- int minX,
- int width,
- int locationY,
- int targetX,
+ Buffer2D background,
+ Buffer2D foreground,
+ Rectangle backgroundRectangle,
+ Rectangle foregroundRectangle,
+ PixelBlender blender,
float opacity)
{
- this.source = source;
- this.target = target;
- this.blender = blender;
this.configuration = configuration;
- this.minX = minX;
- this.width = width;
- this.locationY = locationY;
- this.targetX = targetX;
+ this.background = background;
+ this.foreground = foreground;
+ this.backgroundRectangle = backgroundRectangle;
+ this.foregroundRectangle = foregroundRectangle;
+ this.blender = blender;
this.opacity = opacity;
}
@@ -147,8 +161,8 @@ internal class DrawImageProcessor : ImageProcessor
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
- Span background = this.source.DangerousGetRowSpan(y).Slice(this.minX, this.width);
- Span foreground = this.target.DangerousGetRowSpan(y - this.locationY).Slice(this.targetX, this.width);
+ Span background = this.background.DangerousGetRowSpan(y + this.backgroundRectangle.Top).Slice(this.backgroundRectangle.Left, this.backgroundRectangle.Width);
+ Span foreground = this.foreground.DangerousGetRowSpan(y + this.foregroundRectangle.Top).Slice(this.foregroundRectangle.Left, this.foregroundRectangle.Width);
this.blender.Blend(this.configuration, background, background, foreground, this.opacity);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
index 6352230de2..1491fe073b 100644
--- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
@@ -4,6 +4,7 @@
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -34,17 +35,25 @@ internal class OilPaintingProcessor : ImageProcessor
///
protected override void OnFrameApply(ImageFrame source)
{
+ int levels = Math.Clamp(this.definition.Levels, 1, 255);
int brushSize = Math.Clamp(this.definition.BrushSize, 1, Math.Min(source.Width, source.Height));
using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size());
source.CopyTo(targetPixels);
- RowIntervalOperation operation = new(this.SourceRectangle, targetPixels, source.PixelBuffer, this.Configuration, brushSize >> 1, this.definition.Levels);
- ParallelRowIterator.IterateRowIntervals(
+ RowIntervalOperation operation = new(this.SourceRectangle, targetPixels, source.PixelBuffer, this.Configuration, brushSize >> 1, levels);
+ try
+ {
+ ParallelRowIterator.IterateRowIntervals(
this.Configuration,
this.SourceRectangle,
in operation);
+ }
+ catch (Exception ex)
+ {
+ throw new ImageProcessingException("The OilPaintProcessor failed. The most likely reason is that a pixel component was outside of its' allowed range.", ex);
+ }
Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
@@ -105,18 +114,18 @@ internal class OilPaintingProcessor : ImageProcessor
Span targetRowVector4Span = targetRowBuffer.Memory.Span;
Span targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width);
- ref float binsRef = ref bins.GetReference();
- ref int intensityBinRef = ref Unsafe.As(ref binsRef);
- ref float redBinRef = ref Unsafe.Add(ref binsRef, (uint)this.levels);
- ref float blueBinRef = ref Unsafe.Add(ref redBinRef, (uint)this.levels);
- ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, (uint)this.levels);
+ Span binsSpan = bins.GetSpan();
+ Span intensityBinsSpan = MemoryMarshal.Cast(binsSpan);
+ Span redBinSpan = binsSpan[this.levels..];
+ Span blueBinSpan = redBinSpan[this.levels..];
+ Span greenBinSpan = blueBinSpan[this.levels..];
for (int y = rows.Min; y < rows.Max; y++)
{
Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y);
Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width);
- PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span);
+ PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span, PixelConversionModifiers.Scale);
for (int x = this.bounds.X; x < this.bounds.Right; x++)
{
@@ -140,7 +149,7 @@ internal class OilPaintingProcessor : ImageProcessor
int offsetX = x + fxr;
offsetX = Numerics.Clamp(offsetX, 0, maxX);
- Vector4 vector = sourceOffsetRow[offsetX].ToVector4();
+ Vector4 vector = sourceOffsetRow[offsetX].ToScaledVector4();
float sourceRed = vector.X;
float sourceBlue = vector.Z;
@@ -148,21 +157,21 @@ internal class OilPaintingProcessor : ImageProcessor
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1));
- Unsafe.Add(ref intensityBinRef, (uint)currentIntensity)++;
- Unsafe.Add(ref redBinRef, (uint)currentIntensity) += sourceRed;
- Unsafe.Add(ref blueBinRef, (uint)currentIntensity) += sourceBlue;
- Unsafe.Add(ref greenBinRef, (uint)currentIntensity) += sourceGreen;
+ intensityBinsSpan[currentIntensity]++;
+ redBinSpan[currentIntensity] += sourceRed;
+ blueBinSpan[currentIntensity] += sourceBlue;
+ greenBinSpan[currentIntensity] += sourceGreen;
- if (Unsafe.Add(ref intensityBinRef, (uint)currentIntensity) > maxIntensity)
+ if (intensityBinsSpan[currentIntensity] > maxIntensity)
{
- maxIntensity = Unsafe.Add(ref intensityBinRef, (uint)currentIntensity);
+ maxIntensity = intensityBinsSpan[currentIntensity];
maxIndex = currentIntensity;
}
}
- float red = MathF.Abs(Unsafe.Add(ref redBinRef, (uint)maxIndex) / maxIntensity);
- float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, (uint)maxIndex) / maxIntensity);
- float green = MathF.Abs(Unsafe.Add(ref greenBinRef, (uint)maxIndex) / maxIntensity);
+ float red = redBinSpan[maxIndex] / maxIntensity;
+ float blue = blueBinSpan[maxIndex] / maxIntensity;
+ float green = greenBinSpan[maxIndex] / maxIntensity;
float alpha = sourceRowVector4Span[x].W;
targetRowVector4Span[x] = new Vector4(red, green, blue, alpha);
@@ -171,7 +180,7 @@ internal class OilPaintingProcessor : ImageProcessor
Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
- PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan, PixelConversionModifiers.Scale);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
index 786248339b..0c6ba7ddc9 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
@@ -149,13 +149,13 @@ internal sealed class EuclideanPixelMap : IDisposable
/// The granularity of the cache has been determined based upon the current
/// suite of test images and provides the lowest possible memory usage while
/// providing enough match accuracy.
- /// Entry count is currently limited to 1185921 entries (2371842 bytes ~2.26MB).
+ /// Entry count is currently limited to 2335905 entries (4671810 bytes ~4.45MB).
///
///
private unsafe struct ColorDistanceCache : IDisposable
{
private const int IndexBits = 5;
- private const int IndexAlphaBits = 5;
+ private const int IndexAlphaBits = 6;
private const int IndexCount = (1 << IndexBits) + 1;
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
private const int RgbShift = 8 - IndexBits;
diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets
index c05384fcce..3a29442872 100644
--- a/tests/Directory.Build.targets
+++ b/tests/Directory.Build.targets
@@ -30,7 +30,7 @@
-
+
diff --git a/tests/ImageSharp.Benchmarks/Processing/OilPaint.cs b/tests/ImageSharp.Benchmarks/Processing/OilPaint.cs
new file mode 100644
index 0000000000..239d5a93bc
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Processing/OilPaint.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+
+namespace SixLabors.ImageSharp.Benchmarks.Processing;
+
+[Config(typeof(Config.MultiFramework))]
+public class OilPaint
+{
+ [Benchmark]
+ public void DoOilPaint()
+ {
+ using Image image = new Image(1920, 1200, new(127, 191, 255));
+ image.Mutate(ctx => ctx.OilPaint());
+ }
+}
diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs
index 3853c445f7..c5d61726c8 100644
--- a/tests/ImageSharp.Tests/ConfigurationTests.cs
+++ b/tests/ImageSharp.Tests/ConfigurationTests.cs
@@ -20,7 +20,7 @@ public class ConfigurationTests
public Configuration DefaultConfiguration { get; }
- private readonly int expectedDefaultConfigurationCount = 8;
+ private readonly int expectedDefaultConfigurationCount = 9;
public ConfigurationTests()
{
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
index 26129c5998..59e1bc4d88 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
@@ -13,11 +13,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext()
{
- // non-default values as we cant easly defect usage otherwise
+ // non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
- this.operations.DrawImage(null, 0.5f);
+ using Image image = new(Configuration.Default, 1, 1);
+ this.operations.DrawImage(image, 0.5f);
DrawImageProcessor dip = this.Verify();
Assert.Equal(0.5, dip.Opacity);
@@ -28,11 +29,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext()
{
- // non-default values as we cant easly defect usage otherwise
+ // non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
- this.operations.DrawImage(null, PixelColorBlendingMode.Multiply, 0.5f);
+ using Image image = new(Configuration.Default, 1, 1);
+ this.operations.DrawImage(image, PixelColorBlendingMode.Multiply, 0.5f);
DrawImageProcessor dip = this.Verify();
Assert.Equal(0.5, dip.Opacity);
@@ -43,11 +45,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext()
{
- // non-default values as we cant easly defect usage otherwise
+ // non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
- this.operations.DrawImage(null, Point.Empty, 0.5f);
+ using Image image = new(Configuration.Default, 1, 1);
+ this.operations.DrawImage(image, Point.Empty, 0.5f);
DrawImageProcessor dip = this.Verify();
Assert.Equal(0.5, dip.Opacity);
@@ -58,11 +61,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFromContext()
{
- // non-default values as we cant easly defect usage otherwise
+ // non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
- this.operations.DrawImage(null, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f);
+ using Image image = new(Configuration.Default, 1, 1);
+ this.operations.DrawImage(image, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f);
DrawImageProcessor dip = this.Verify();
Assert.Equal(0.5, dip.Opacity);
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
index d017e5ad42..8b0db773ab 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
@@ -190,18 +190,65 @@ public class DrawImageTests
}
[Theory]
- [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, -30)]
- [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, -30)]
- [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, 130)]
- [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, 130)]
- public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y)
+ [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)]
+ public void Issue2447_A(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
{
- using Image background = provider.GetImage();
- using Image overlay = new(Configuration.Default, 10, 10, Color.Black);
- ImageProcessingException ex = Assert.Throws(Test);
+ using Image foreground = provider.GetImage();
+ using Image background = new(100, 100, new Rgba32(0, 255, 255));
- Assert.Contains("does not overlap", ex.ToString());
+ background.Mutate(c => c.DrawImage(foreground, new Point(64, 10), new Rectangle(32, 32, 32, 32), 1F));
- void Test() => background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions()));
+ background.DebugSave(
+ provider,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+
+ background.CompareToReferenceOutput(
+ provider,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)]
+ public void Issue2447_B(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image foreground = provider.GetImage();
+ using Image background = new(100, 100, new Rgba32(0, 255, 255));
+
+ background.Mutate(c => c.DrawImage(foreground, new Point(10, 10), new Rectangle(320, 128, 32, 32), 1F));
+
+ background.DebugSave(
+ provider,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+
+ background.CompareToReferenceOutput(
+ provider,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)]
+ public void Issue2447_C(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image foreground = provider.GetImage();
+ using Image background = new(100, 100, new Rgba32(0, 255, 255));
+
+ background.Mutate(c => c.DrawImage(foreground, new Point(10, 10), new Rectangle(32, 32, 32, 32), 1F));
+
+ background.DebugSave(
+ provider,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+
+ background.CompareToReferenceOutput(
+ provider,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
index f486310b78..a0d91c2088 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
@@ -370,6 +370,30 @@ public class BmpEncoderTests
TestBmpEncoderCore(provider, bitsPerPixel);
}
+ [Theory]
+ [WithFile(BlackWhitePalletDataMatrix, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel1)]
+ public void Encode_Issue2467(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage();
+
+ using var reencodedStream = new MemoryStream();
+ var encoder = new BmpEncoder
+ {
+ BitsPerPixel = bitsPerPixel,
+ SupportTransparency = false,
+ Quantizer = KnownQuantizers.Octree
+ };
+ image.SaveAsBmp(reencodedStream, encoder);
+ reencodedStream.Seek(0, SeekOrigin.Begin);
+
+ using Image reencodedImage = Image.Load(reencodedStream);
+
+ reencodedImage.DebugSave(provider);
+
+ reencodedImage.CompareToOriginal(provider);
+ }
+
private static void TestBmpEncoderCore(
TestImageProvider provider,
BmpBitsPerPixel bitsPerPixel,
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
index b4facfa3fe..376bb4a06f 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
@@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
@@ -51,10 +51,11 @@ public class GifDecoderTests
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
- // Floating point differences result in minor pixel differences.
+ // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences.
// Output have been manually verified.
+ // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594
image.CompareToReferenceOutput(
- ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0002F : 0.0001F),
+ ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0001F : 0.0002F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index 80789178d1..eaa9f82cbb 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -314,4 +314,32 @@ public partial class JpegDecoderTests
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
+
+ // https://github.com/SixLabors/ImageSharp/issues/2478
+ [Theory]
+ [WithFile(TestImages.Jpeg.Issues.Issue2478_JFXX, PixelTypes.Rgba32)]
+ public void Issue2478_DecodeWorks(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(JpegDecoder.Instance);
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Jpeg.Issues.HangBadScan, PixelTypes.L8)]
+ public void DecodeHang(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ if (TestEnvironment.IsWindows &&
+ TestEnvironment.RunsOnCI)
+ {
+ // Windows CI runs consistently fail with OOM.
+ return;
+ }
+
+ using Image image = provider.GetImage(JpegDecoder.Instance);
+ Assert.Equal(65503, image.Width);
+ Assert.Equal(65503, image.Height);
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
index 5bdab7b37a..1b57663f3a 100644
--- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
@@ -81,6 +81,7 @@ public class PbmDecoderTests
[Theory]
[WithFile(BlackAndWhitePlain, PixelTypes.L8, "pbm")]
[WithFile(BlackAndWhiteBinary, PixelTypes.L8, "pbm")]
+ [WithFile(Issue2477, PixelTypes.L8, "pbm")]
[WithFile(GrayscalePlain, PixelTypes.L8, "pgm")]
[WithFile(GrayscalePlainNormalized, PixelTypes.L8, "pgm")]
[WithFile(GrayscaleBinary, PixelTypes.L8, "pgm")]
diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs
index a0a4c1164f..05f1d963b2 100644
--- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs
@@ -26,6 +26,7 @@ public class PbmEncoderTests
{
{ BlackAndWhiteBinary, PbmColorType.BlackAndWhite },
{ BlackAndWhitePlain, PbmColorType.BlackAndWhite },
+ { Issue2477, PbmColorType.BlackAndWhite },
{ GrayscaleBinary, PbmColorType.Grayscale },
{ GrayscaleBinaryWide, PbmColorType.Grayscale },
{ GrayscalePlain, PbmColorType.Grayscale },
@@ -96,6 +97,11 @@ public class PbmEncoderTests
public void PbmEncoder_P4_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary);
+ [Theory]
+ [WithFile(Issue2477, PixelTypes.Rgb24)]
+ public void PbmEncoder_P4_Irregular_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary);
+
[Theory]
[WithFile(GrayscalePlainMagick, PixelTypes.Rgb24)]
public void PbmEncoder_P2_Works(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
index 499607772f..c40ec7318a 100644
--- a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Pbm;
using static SixLabors.ImageSharp.Tests.TestImages.Pbm;
@@ -80,4 +81,14 @@ public class PbmMetadataTests
Assert.NotNull(bitmapMetadata);
Assert.Equal(expectedComponentType, bitmapMetadata.ComponentType);
}
+
+ [Fact]
+ public void Identify_HandlesCraftedDenialOfServiceString()
+ {
+ byte[] bytes = Convert.FromBase64String("UDEjWAAACQAAAAA=");
+ ImageInfo info = Image.Identify(bytes);
+ Assert.Equal(default, info.Size);
+ Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("pbm", out IImageFormat format);
+ Assert.Equal(format!, info.Metadata.DecodedImageFormat);
+ }
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index afd33608ce..2dfd99439a 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -1,12 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
@@ -122,15 +123,52 @@ public partial class PngDecoderTests
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
- // Floating point differences result in minor pixel differences.
+ // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences.
// Output have been manually verified.
+ // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594
image.CompareToReferenceOutput(
- ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0005F : 0.0003F),
+ ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0003F : 0.0005F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
}
+ [Theory]
+ [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)]
+ public void PngDecoder_Decode_Resize_ScalarResizeKernel(TestImageProvider provider)
+ {
+ HwIntrinsics intrinsicsFilter = HwIntrinsics.DisableHWIntrinsic;
+
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ RunTest,
+ intrinsicsFilter,
+ provider,
+ string.Empty);
+
+ static void RunTest(string arg1, string notUsed)
+ {
+ TestImageProvider provider =
+ FeatureTestRunner.DeserializeForXunit>(arg1);
+
+ DecoderOptions options = new()
+ {
+ TargetSize = new() { Width = 150, Height = 150 }
+ };
+
+ using Image image = provider.GetImage(PngDecoder.Instance, options);
+
+ FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
+
+ image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
+
+ image.CompareToReferenceOutput(
+ ImageComparer.TolerantPercentage(0.0005F),
+ provider,
+ testOutputDetails: details,
+ appendPixelTypeToFileName: false);
+ }
+ }
+
[Theory]
[WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)]
@@ -500,7 +538,6 @@ public partial class PngDecoderTests
where TPixel : unmanaged, IPixel
{
using Image image = provider.GetImage(PngDecoder.Instance);
- image.DebugSave(provider);
PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency);
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 74038c7616..b4fda5d32f 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -129,8 +129,8 @@ public partial class PngEncoderTests
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
- appendPixelType: true,
- appendPngColorType: true);
+ appendPngColorType: true,
+ appendPixelType: true);
}
}
@@ -321,7 +321,7 @@ public partial class PngEncoderTests
where TPixel : unmanaged, IPixel
{
using Image image = provider.GetImage();
- using var ms = new MemoryStream();
+ using MemoryStream ms = new();
image.Save(ms, PngEncoder);
byte[] data = ms.ToArray().Take(8).ToArray();
@@ -344,13 +344,13 @@ public partial class PngEncoderTests
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
- var testFile = TestFile.Create(imagePath);
+ TestFile testFile = TestFile.Create(imagePath);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
- using var output = Image.Load(memStream);
+ using Image output = Image.Load(memStream);
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@@ -361,13 +361,13 @@ public partial class PngEncoderTests
[MemberData(nameof(PngBitDepthFiles))]
public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
{
- var testFile = TestFile.Create(imagePath);
+ TestFile testFile = TestFile.Create(imagePath);
using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
- using var output = Image.Load(memStream);
+ using Image output = Image.Load(memStream);
PngMetadata meta = output.Metadata.GetPngMetadata();
Assert.Equal(pngBitDepth, meta.BitDepth);
@@ -380,8 +380,8 @@ public partial class PngEncoderTests
public void Encode_WithPngTransparentColorBehaviorClear_Works(PngColorType colorType)
{
// arrange
- var image = new Image(50, 50);
- var encoder = new PngEncoder()
+ Image image = new(50, 50);
+ PngEncoder encoder = new()
{
TransparentColorMode = PngTransparentColorMode.Clear,
ColorType = colorType
@@ -391,7 +391,7 @@ public partial class PngEncoderTests
{
for (int y = 0; y < image.Height; y++)
{
- System.Span rowSpan = accessor.GetRowSpan(y);
+ Span rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
@@ -407,12 +407,12 @@ public partial class PngEncoderTests
});
// act
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
- using var actual = Image.Load(memStream);
+ using Image actual = Image.Load(memStream);
Rgba32 expectedColor = Color.Blue;
if (colorType is PngColorType.Grayscale or PngColorType.GrayscaleWithAlpha)
{
@@ -424,7 +424,7 @@ public partial class PngEncoderTests
{
for (int y = 0; y < accessor.Height; y++)
{
- System.Span rowSpan = accessor.GetRowSpan(y);
+ Span rowSpan = accessor.GetRowSpan(y);
if (y > 25)
{
@@ -443,15 +443,15 @@ public partial class PngEncoderTests
[MemberData(nameof(PngTrnsFiles))]
public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)
{
- var testFile = TestFile.Create(imagePath);
+ TestFile testFile = TestFile.Create(imagePath);
using Image input = testFile.CreateRgba32Image();
PngMetadata inMeta = input.Metadata.GetPngMetadata();
Assert.True(inMeta.HasTransparency);
- using var memStream = new MemoryStream();
+ using MemoryStream memStream = new();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
- using var output = Image.Load(memStream);
+ using Image output = Image.Load(memStream);
PngMetadata outMeta = output.Metadata.GetPngMetadata();
Assert.True(outMeta.HasTransparency);
@@ -501,8 +501,8 @@ public partial class PngEncoderTests
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
- appendPixelType: true,
- appendPngColorType: true);
+ appendPngColorType: true,
+ appendPixelType: true);
}
}
@@ -523,8 +523,8 @@ public partial class PngEncoderTests
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
- appendPixelType: true,
- appendPngColorType: true);
+ appendPngColorType: true,
+ appendPixelType: true);
}
}
@@ -538,13 +538,27 @@ public partial class PngEncoderTests
public void EncodeFixesInvalidOptions()
{
// https://github.com/SixLabors/ImageSharp/issues/935
- using var ms = new MemoryStream();
- var testFile = TestFile.Create(TestImages.Png.Issue935);
+ using MemoryStream ms = new();
+ TestFile testFile = TestFile.Create(TestImages.Png.Issue935);
using Image image = testFile.CreateRgba32Image(PngDecoder.Instance);
image.Save(ms, new PngEncoder { ColorType = PngColorType.RgbWithAlpha });
}
+ // https://github.com/SixLabors/ImageSharp/issues/2469
+ [Theory]
+ [WithFile(TestImages.Png.Issue2469, PixelTypes.Rgba32)]
+ public void Issue2469_Quantized_Encode_Artifacts(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(PngDecoder.Instance);
+ PngEncoder encoder = new() { BitDepth = PngBitDepth.Bit8, ColorType = PngColorType.Palette };
+
+ string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder);
+ using Image encoded = Image.Load(actualOutputFile);
+ encoded.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+
private static void TestPngEncoderCore(
TestImageProvider provider,
PngColorType pngColorType,
@@ -563,7 +577,7 @@ public partial class PngEncoderTests
where TPixel : unmanaged, IPixel
{
using Image image = provider.GetImage();
- var encoder = new PngEncoder
+ PngEncoder encoder = new()
{
ColorType = pngColorType,
FilterMethod = pngFilterMethod,
diff --git a/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs
new file mode 100644
index 0000000000..31ec27da0c
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs
@@ -0,0 +1,135 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Qoi;
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Qoi;
+
+public class ImageExtensionsTest
+{
+ [Fact]
+ public void SaveAsQoi_Path()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
+ string file = Path.Combine(dir, "SaveAsQoi_Path.qoi");
+
+ using (Image image = new(10, 10))
+ {
+ image.SaveAsQoi(file);
+ }
+
+ IImageFormat format = Image.DetectFormat(file);
+ Assert.True(format is QoiFormat);
+ }
+
+ [Fact]
+ public async Task SaveAsQoiAsync_Path()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
+ string file = Path.Combine(dir, "SaveAsQoiAsync_Path.qoi");
+
+ using (Image image = new(10, 10))
+ {
+ await image.SaveAsQoiAsync(file);
+ }
+
+ IImageFormat format = Image.DetectFormat(file);
+ Assert.True(format is QoiFormat);
+ }
+
+ [Fact]
+ public void SaveAsQoi_Path_Encoder()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
+ string file = Path.Combine(dir, "SaveAsQoi_Path_Encoder.qoi");
+
+ using (Image image = new(10, 10))
+ {
+ image.SaveAsQoi(file, new QoiEncoder());
+ }
+
+ IImageFormat format = Image.DetectFormat(file);
+ Assert.True(format is QoiFormat);
+ }
+
+ [Fact]
+ public async Task SaveAsQoiAsync_Path_Encoder()
+ {
+ string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
+ string file = Path.Combine(dir, "SaveAsQoiAsync_Path_Encoder.qoi");
+
+ using (Image image = new(10, 10))
+ {
+ await image.SaveAsQoiAsync(file, new QoiEncoder());
+ }
+
+ IImageFormat format = Image.DetectFormat(file);
+ Assert.True(format is QoiFormat);
+ }
+
+ [Fact]
+ public void SaveAsQoi_Stream()
+ {
+ using MemoryStream memoryStream = new();
+
+ using (Image image = new(10, 10))
+ {
+ image.SaveAsQoi(memoryStream);
+ }
+
+ memoryStream.Position = 0;
+
+ IImageFormat format = Image.DetectFormat(memoryStream);
+ Assert.True(format is QoiFormat);
+ }
+
+ [Fact]
+ public async Task SaveAsQoiAsync_StreamAsync()
+ {
+ using MemoryStream memoryStream = new();
+
+ using (Image image = new(10, 10))
+ {
+ await image.SaveAsQoiAsync(memoryStream);
+ }
+
+ memoryStream.Position = 0;
+
+ IImageFormat format = Image.DetectFormat(memoryStream);
+ Assert.True(format is QoiFormat);
+ }
+
+ [Fact]
+ public void SaveAsQoi_Stream_Encoder()
+ {
+ using MemoryStream memoryStream = new();
+
+ using (Image image = new(10, 10))
+ {
+ image.SaveAsQoi(memoryStream, new QoiEncoder());
+ }
+
+ memoryStream.Position = 0;
+
+ IImageFormat format = Image.DetectFormat(memoryStream);
+ Assert.True(format is QoiFormat);
+ }
+
+ [Fact]
+ public async Task SaveAsQoiAsync_Stream_Encoder()
+ {
+ using MemoryStream memoryStream = new();
+
+ using (Image image = new(10, 10))
+ {
+ await image.SaveAsQoiAsync(memoryStream, new QoiEncoder());
+ }
+
+ memoryStream.Position = 0;
+
+ IImageFormat format = Image.DetectFormat(memoryStream);
+ Assert.True(format is QoiFormat);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs
new file mode 100644
index 0000000000..387980e464
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Qoi;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Qoi;
+
+[Trait("Format", "Qoi")]
+[ValidateDisposedMemoryAllocations]
+public class QoiDecoderTests
+{
+ [Theory]
+ [InlineData(TestImages.Qoi.Dice, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [InlineData(TestImages.Qoi.EdgeCase, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [InlineData(TestImages.Qoi.Kodim10, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ [InlineData(TestImages.Qoi.Kodim23, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ [InlineData(TestImages.Qoi.QoiLogo, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [InlineData(TestImages.Qoi.TestCard, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [InlineData(TestImages.Qoi.TestCardRGBA, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [InlineData(TestImages.Qoi.Wikipedia008, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ public void Identify(string imagePath, QoiChannels channels, QoiColorSpace colorSpace)
+ {
+ TestFile testFile = TestFile.Create(imagePath);
+ using MemoryStream stream = new(testFile.Bytes, false);
+
+ ImageInfo imageInfo = Image.Identify(stream);
+ QoiMetadata qoiMetadata = imageInfo.Metadata.GetQoiMetadata();
+
+ Assert.NotNull(imageInfo);
+ Assert.Equal(imageInfo.Metadata.DecodedImageFormat, QoiFormat.Instance);
+ Assert.Equal(qoiMetadata.Channels, channels);
+ Assert.Equal(qoiMetadata.ColorSpace, colorSpace);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Qoi.Dice, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.EdgeCase, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.Kodim10, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.Kodim23, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.QoiLogo, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.TestCard, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.TestCardRGBA, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.Wikipedia008, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ public void Decode(TestImageProvider provider, QoiChannels channels, QoiColorSpace colorSpace)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage();
+ QoiMetadata qoiMetadata = image.Metadata.GetQoiMetadata();
+ image.DebugSave(provider);
+
+ image.CompareToReferenceOutput(provider);
+ Assert.Equal(qoiMetadata.Channels, channels);
+ Assert.Equal(qoiMetadata.ColorSpace, colorSpace);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
new file mode 100644
index 0000000000..d57b597b06
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Qoi/QoiEncoderTests.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Qoi;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Qoi;
+
+[Trait("Format", "Qoi")]
+[ValidateDisposedMemoryAllocations]
+public class QoiEncoderTests
+{
+ [Theory]
+ [WithFile(TestImages.Qoi.Dice, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.EdgeCase, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.Kodim10, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.Kodim23, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.QoiLogo, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.TestCard, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.TestCardRGBA, PixelTypes.Rgba32, QoiChannels.Rgba, QoiColorSpace.SrgbWithLinearAlpha)]
+ [WithFile(TestImages.Qoi.Wikipedia008, PixelTypes.Rgba32, QoiChannels.Rgb, QoiColorSpace.SrgbWithLinearAlpha)]
+ public static void Encode(TestImageProvider provider, QoiChannels channels, QoiColorSpace colorSpace)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(new MagickReferenceDecoder());
+ using MemoryStream stream = new();
+ QoiEncoder encoder = new()
+ {
+ Channels = channels,
+ ColorSpace = colorSpace
+ };
+ image.Save(stream, encoder);
+ stream.Position = 0;
+
+ using Image encodedImage = (Image)Image.Load(stream);
+ QoiMetadata qoiMetadata = encodedImage.Metadata.GetQoiMetadata();
+
+ ImageComparer.Exact.CompareImages(image, encodedImage);
+ Assert.Equal(qoiMetadata.Channels, channels);
+ Assert.Equal(qoiMetadata.ColorSpace, colorSpace);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
index 0bbe1984f0..da5de8e898 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tga;
@@ -760,10 +761,11 @@ public class TgaDecoderTests
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
- // Floating point differences result in minor pixel differences.
+ // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences.
// Output have been manually verified.
+ // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594
image.CompareToReferenceOutput(
- ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0016F : 0.0001F),
+ ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0001F : 0.0016F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
index 72b87bcf02..2c8268d413 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
@@ -3,6 +3,7 @@
// ReSharper disable InconsistentNaming
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
@@ -200,11 +201,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
-
- TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.264F);
- }
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.264F);
[Theory]
[WithFile(Flower14BitGray, PixelTypes.Rgba32)]
@@ -249,11 +246,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
-
- TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.376F);
- }
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.376F);
[Theory]
[WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)]
@@ -268,11 +261,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
-
- TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.405F);
- }
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.405F);
[Theory]
[WithFile(Flower24BitGray, PixelTypes.Rgba32)]
@@ -319,6 +308,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Cmyk, PixelTypes.Rgba32)]
+ [WithFile(CmykLzwPredictor, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Cmyk(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
@@ -362,12 +352,8 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Rgba8BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_32Bit_WithAssociatedAlpha(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
-
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
- TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F);
- }
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F);
[Theory]
[WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)]
@@ -395,11 +381,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
-
- TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.247F);
- }
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.247F);
[Theory]
[WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)]
@@ -426,11 +408,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
-
- TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.118F);
- }
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.118F);
[Theory]
[WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)]
@@ -448,11 +426,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
-
- TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.075F);
- }
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.075F);
[Theory]
[WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)]
@@ -686,6 +660,12 @@ public class TiffDecoderTests : TiffDecoderBaseTester
public void TiffDecoder_CanDecode_Fax4CompressedWithStrips(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+ // https://github.com/SixLabors/ImageSharp/issues/2435
+ [Theory]
+ [WithFile(Issues2435, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_TiledWithNonEqualWidthAndHeight(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+
[Theory]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]
public void DecodeMultiframe(TestImageProvider provider)
@@ -717,10 +697,11 @@ public class TiffDecoderTests : TiffDecoderBaseTester
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
- // Floating point differences result in minor pixel differences.
+ // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences.
// Output have been manually verified.
+ // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594
image.CompareToReferenceOutput(
- TestEnvironment.OSArchitecture == Architecture.Arm64 ? ImageComparer.TolerantPercentage(0.0006F) : ImageComparer.Exact,
+ Fma.IsSupported ? ImageComparer.Exact : ImageComparer.TolerantPercentage(0.0006F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs
index 1f9136e9ab..4982929c2c 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Text;
+using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities;
@@ -9,10 +11,234 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp;
[Trait("Format", "Webp")]
public class Vp8ResidualTests
{
+ private static void WriteVp8Residual(string filename, Vp8Residual residual)
+ {
+ using FileStream stream = File.Open(filename, FileMode.Create);
+ using BinaryWriter writer = new(stream, Encoding.UTF8, false);
+
+ writer.Write(residual.First);
+ writer.Write(residual.Last);
+ writer.Write(residual.CoeffType);
+
+ for (int i = 0; i < residual.Coeffs.Length; i++)
+ {
+ writer.Write(residual.Coeffs[i]);
+ }
+
+ for (int i = 0; i < residual.Prob.Length; i++)
+ {
+ for (int j = 0; j < residual.Prob[i].Probabilities.Length; j++)
+ {
+ writer.Write(residual.Prob[i].Probabilities[j].Probabilities);
+ }
+ }
+
+ for (int i = 0; i < residual.Costs.Length; i++)
+ {
+ Vp8Costs costs = residual.Costs[i];
+ Vp8CostArray[] costsArray = costs.Costs;
+ for (int j = 0; j < costsArray.Length; j++)
+ {
+ for (int k = 0; k < costsArray[j].Costs.Length; k++)
+ {
+ writer.Write(costsArray[j].Costs[k]);
+ }
+ }
+ }
+
+ for (int i = 0; i < residual.Stats.Length; i++)
+ {
+ for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
+ {
+ for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
+ {
+ writer.Write(residual.Stats[i].Stats[j].Stats[k]);
+ }
+ }
+ }
+
+ writer.Flush();
+ }
+
+ private static Vp8Residual ReadVp8Residual(string fileName)
+ {
+ using FileStream stream = File.Open(fileName, FileMode.Open);
+ using BinaryReader reader = new(stream, Encoding.UTF8, false);
+
+ Vp8Residual residual = new()
+ {
+ First = reader.ReadInt32(),
+ Last = reader.ReadInt32(),
+ CoeffType = reader.ReadInt32()
+ };
+
+ for (int i = 0; i < residual.Coeffs.Length; i++)
+ {
+ residual.Coeffs[i] = reader.ReadInt16();
+ }
+
+ Vp8BandProbas[] bandProbas = new Vp8BandProbas[8];
+ for (int i = 0; i < bandProbas.Length; i++)
+ {
+ bandProbas[i] = new Vp8BandProbas();
+ for (int j = 0; j < bandProbas[i].Probabilities.Length; j++)
+ {
+ for (int k = 0; k < 11; k++)
+ {
+ bandProbas[i].Probabilities[j].Probabilities[k] = reader.ReadByte();
+ }
+ }
+ }
+
+ residual.Prob = bandProbas;
+
+ residual.Costs = new Vp8Costs[16];
+ for (int i = 0; i < residual.Costs.Length; i++)
+ {
+ residual.Costs[i] = new Vp8Costs();
+ Vp8CostArray[] costsArray = residual.Costs[i].Costs;
+ for (int j = 0; j < costsArray.Length; j++)
+ {
+ for (int k = 0; k < costsArray[j].Costs.Length; k++)
+ {
+ costsArray[j].Costs[k] = reader.ReadUInt16();
+ }
+ }
+ }
+
+ residual.Stats = new Vp8Stats[8];
+ for (int i = 0; i < residual.Stats.Length; i++)
+ {
+ residual.Stats[i] = new Vp8Stats();
+ for (int j = 0; j < residual.Stats[i].Stats.Length; j++)
+ {
+ for (int k = 0; k < residual.Stats[i].Stats[j].Stats.Length; k++)
+ {
+ residual.Stats[i].Stats[j].Stats[k] = reader.ReadUInt32();
+ }
+ }
+ }
+
+ return residual;
+ }
+
+ [Fact]
+ public void Vp8Residual_Serialization_Works()
+ {
+ // arrange
+ Vp8Residual expected = new();
+ Vp8EncProba encProb = new();
+ Random rand = new(439);
+ CreateRandomProbas(encProb, rand);
+ CreateCosts(encProb, rand);
+ expected.Init(1, 0, encProb);
+ for (int i = 0; i < expected.Coeffs.Length; i++)
+ {
+ expected.Coeffs[i] = (byte)rand.Next(255);
+ }
+
+ // act
+ string fileName = "Vp8SerializationTest.bin";
+ WriteVp8Residual(fileName, expected);
+ Vp8Residual actual = ReadVp8Residual(fileName);
+ File.Delete(fileName);
+
+ // assert
+ Assert.Equal(expected.CoeffType, actual.CoeffType);
+ Assert.Equal(expected.Coeffs, actual.Coeffs);
+ Assert.Equal(expected.Costs.Length, actual.Costs.Length);
+ Assert.Equal(expected.First, actual.First);
+ Assert.Equal(expected.Last, actual.Last);
+ Assert.Equal(expected.Stats.Length, actual.Stats.Length);
+ for (int i = 0; i < expected.Stats.Length; i++)
+ {
+ Vp8StatsArray[] expectedStats = expected.Stats[i].Stats;
+ Vp8StatsArray[] actualStats = actual.Stats[i].Stats;
+ Assert.Equal(expectedStats.Length, actualStats.Length);
+ for (int j = 0; j < expectedStats.Length; j++)
+ {
+ Assert.Equal(expectedStats[j].Stats, actualStats[j].Stats);
+ }
+ }
+
+ Assert.Equal(expected.Prob.Length, actual.Prob.Length);
+ for (int i = 0; i < expected.Prob.Length; i++)
+ {
+ Vp8ProbaArray[] expectedProbabilities = expected.Prob[i].Probabilities;
+ Vp8ProbaArray[] actualProbabilities = actual.Prob[i].Probabilities;
+ Assert.Equal(expectedProbabilities.Length, actualProbabilities.Length);
+ for (int j = 0; j < expectedProbabilities.Length; j++)
+ {
+ Assert.Equal(expectedProbabilities[j].Probabilities, actualProbabilities[j].Probabilities);
+ }
+ }
+
+ for (int i = 0; i < expected.Costs.Length; i++)
+ {
+ Vp8CostArray[] expectedCosts = expected.Costs[i].Costs;
+ Vp8CostArray[] actualCosts = actual.Costs[i].Costs;
+ Assert.Equal(expectedCosts.Length, actualCosts.Length);
+ for (int j = 0; j < expectedCosts.Length; j++)
+ {
+ Assert.Equal(expectedCosts[j].Costs, actualCosts[j].Costs);
+ }
+ }
+ }
+
+ [Fact]
+ public void GetResidualCost_Works()
+ {
+ // arrange
+ int ctx0 = 0;
+ int expected = 20911;
+ Vp8Residual residual = ReadVp8Residual(Path.Combine("TestDataWebp", "Vp8Residual.bin"));
+
+ // act
+ int actual = residual.GetResidualCost(ctx0);
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ private static void CreateRandomProbas(Vp8EncProba probas, Random rand)
+ {
+ for (int t = 0; t < WebpConstants.NumTypes; ++t)
+ {
+ for (int b = 0; b < WebpConstants.NumBands; ++b)
+ {
+ for (int c = 0; c < WebpConstants.NumCtx; ++c)
+ {
+ for (int p = 0; p < WebpConstants.NumProbas; ++p)
+ {
+ probas.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)rand.Next(255);
+ }
+ }
+ }
+ }
+ }
+
+ private static void CreateCosts(Vp8EncProba probas, Random rand)
+ {
+ for (int i = 0; i < probas.RemappedCosts.Length; i++)
+ {
+ for (int j = 0; j < probas.RemappedCosts[i].Length; j++)
+ {
+ for (int k = 0; k < probas.RemappedCosts[i][j].Costs.Length; k++)
+ {
+ ushort[] costs = probas.RemappedCosts[i][j].Costs[k].Costs;
+ for (int m = 0; m < costs.Length; m++)
+ {
+ costs[m] = (byte)rand.Next(255);
+ }
+ }
+ }
+ }
+ }
+
private static void RunSetCoeffsTest()
{
// arrange
- var residual = new Vp8Residual();
+ Vp8Residual residual = new();
short[] coeffs = { 110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 };
// act
@@ -23,11 +249,8 @@ public class Vp8ResidualTests
}
[Fact]
- public void RunSetCoeffsTest_Works() => RunSetCoeffsTest();
-
- [Fact]
- public void RunSetCoeffsTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);
+ public void SetCoeffsTest_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll);
[Fact]
- public void RunSetCoeffsTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic);
+ public void SetCoeffsTest_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableSSE2);
}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
index 010af3fbbe..f95b003d0a 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.PixelFormats;
@@ -367,10 +368,11 @@ public class WebpDecoderTests
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
- // Floating point differences result in minor pixel differences.
+ // Floating point differences in FMA used in the ResizeKernel result in minor pixel differences.
// Output have been manually verified.
+ // For more details see discussion: https://github.com/SixLabors/ImageSharp/pull/1513#issuecomment-763643594
image.CompareToReferenceOutput(
- ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0156F : 0.0007F),
+ ImageComparer.TolerantPercentage(Fma.IsSupported ? 0.0007F : 0.0156F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);
diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
index 1700b4a734..d393850d6b 100644
--- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
+++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
@@ -2,6 +2,8 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
+using System.Runtime.CompilerServices;
+using Castle.Core.Configuration;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -406,6 +408,43 @@ public class ParallelRowIteratorTests
Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message);
}
+ [Fact]
+ public void CanIterateWithoutIntOverflow()
+ {
+ ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(Configuration.Default);
+ const int max = 100_000;
+
+ Rectangle rect = new(0, 0, max, max);
+ int intervalMaxY = 0;
+ void RowAction(RowInterval rows, Span memory) => intervalMaxY = Math.Max(rows.Max, intervalMaxY);
+
+ TestRowOperation operation = new();
+ TestRowIntervalOperation intervalOperation = new(RowAction);
+
+ ParallelRowIterator.IterateRows(Configuration.Default, rect, in operation);
+ Assert.Equal(max - 1, operation.MaxY.Value);
+
+ ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in intervalOperation);
+ Assert.Equal(max, intervalMaxY);
+ }
+
+ private readonly struct TestRowOperation : IRowOperation
+ {
+ public TestRowOperation()
+ {
+ }
+
+ public StrongBox MaxY { get; } = new StrongBox();
+
+ public void Invoke(int y)
+ {
+ lock (this.MaxY)
+ {
+ this.MaxY.Value = Math.Max(y, this.MaxY.Value);
+ }
+ }
+ }
+
private readonly struct TestRowIntervalOperation : IRowIntervalOperation
{
private readonly Action action;
diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs
index 3d512b7d27..a1eeb25976 100644
--- a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs
+++ b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
@@ -11,36 +11,113 @@ public class LocalFileSystemTests
public void OpenRead()
{
string path = Path.GetTempFileName();
- string testData = Guid.NewGuid().ToString();
- File.WriteAllText(path, testData);
+ try
+ {
+ string testData = Guid.NewGuid().ToString();
+ File.WriteAllText(path, testData);
- var fs = new LocalFileSystem();
+ LocalFileSystem fs = new();
- using (var r = new StreamReader(fs.OpenRead(path)))
- {
- string data = r.ReadToEnd();
+ using (FileStream stream = (FileStream)fs.OpenRead(path))
+ using (StreamReader reader = new(stream))
+ {
+ Assert.False(stream.IsAsync);
+ Assert.True(stream.CanRead);
+ Assert.False(stream.CanWrite);
- Assert.Equal(testData, data);
+ string data = reader.ReadToEnd();
+
+ Assert.Equal(testData, data);
+ }
}
+ finally
+ {
+ File.Delete(path);
+ }
+ }
- File.Delete(path);
+ [Fact]
+ public async Task OpenReadAsynchronous()
+ {
+ string path = Path.GetTempFileName();
+ try
+ {
+ string testData = Guid.NewGuid().ToString();
+ File.WriteAllText(path, testData);
+
+ LocalFileSystem fs = new();
+
+ await using (FileStream stream = (FileStream)fs.OpenReadAsynchronous(path))
+ using (StreamReader reader = new(stream))
+ {
+ Assert.True(stream.IsAsync);
+ Assert.True(stream.CanRead);
+ Assert.False(stream.CanWrite);
+
+ string data = await reader.ReadToEndAsync();
+
+ Assert.Equal(testData, data);
+ }
+ }
+ finally
+ {
+ File.Delete(path);
+ }
}
[Fact]
public void Create()
{
string path = Path.GetTempFileName();
- string testData = Guid.NewGuid().ToString();
- var fs = new LocalFileSystem();
+ try
+ {
+ string testData = Guid.NewGuid().ToString();
+ LocalFileSystem fs = new();
+
+ using (FileStream stream = (FileStream)fs.Create(path))
+ using (StreamWriter writer = new(stream))
+ {
+ Assert.False(stream.IsAsync);
+ Assert.True(stream.CanRead);
+ Assert.True(stream.CanWrite);
- using (var r = new StreamWriter(fs.Create(path)))
+ writer.Write(testData);
+ }
+
+ string data = File.ReadAllText(path);
+ Assert.Equal(testData, data);
+ }
+ finally
{
- r.Write(testData);
+ File.Delete(path);
}
+ }
- string data = File.ReadAllText(path);
- Assert.Equal(testData, data);
+ [Fact]
+ public async Task CreateAsynchronous()
+ {
+ string path = Path.GetTempFileName();
+ try
+ {
+ string testData = Guid.NewGuid().ToString();
+ LocalFileSystem fs = new();
+
+ await using (FileStream stream = (FileStream)fs.CreateAsynchronous(path))
+ await using (StreamWriter writer = new(stream))
+ {
+ Assert.True(stream.IsAsync);
+ Assert.True(stream.CanRead);
+ Assert.True(stream.CanWrite);
+
+ await writer.WriteAsync(testData);
+ }
- File.Delete(path);
+ string data = File.ReadAllText(path);
+ Assert.Equal(testData, data);
+ }
+ finally
+ {
+ File.Delete(path);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs
index a3f03bed5a..f9c01ab564 100644
--- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs
+++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs
@@ -44,7 +44,7 @@ public class ImageSaveTests : IDisposable
[Fact]
public void SavePath()
{
- var stream = new MemoryStream();
+ using MemoryStream stream = new();
this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream);
this.image.Save("path.png");
@@ -54,7 +54,7 @@ public class ImageSaveTests : IDisposable
[Fact]
public void SavePathWithEncoder()
{
- var stream = new MemoryStream();
+ using MemoryStream stream = new();
this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream);
this.image.Save("path.jpg", this.encoderNotInFormat.Object);
@@ -73,7 +73,7 @@ public class ImageSaveTests : IDisposable
[Fact]
public void SaveStreamWithMime()
{
- var stream = new MemoryStream();
+ using MemoryStream stream = new();
this.image.Save(stream, this.localImageFormat.Object);
this.encoder.Verify(x => x.Encode(this.image, stream));
@@ -82,7 +82,7 @@ public class ImageSaveTests : IDisposable
[Fact]
public void SaveStreamWithEncoder()
{
- var stream = new MemoryStream();
+ using MemoryStream stream = new();
this.image.Save(stream, this.encoderNotInFormat.Object);
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
index a8f9981b44..996310d8c3 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
@@ -122,6 +122,7 @@ public partial class ImageTests
Stream StreamFactory() => this.DataStream;
this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory);
+ this.LocalFileSystemMock.Setup(x => x.OpenReadAsynchronous(this.MockFilePath)).Returns(StreamFactory);
this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory);
this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object;
this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem;
@@ -132,6 +133,11 @@ public partial class ImageTests
// Clean up the global object;
this.localStreamReturnImageRgba32?.Dispose();
this.localStreamReturnImageAgnostic?.Dispose();
+
+ if (this.dataStreamLazy.IsValueCreated)
+ {
+ this.dataStreamLazy.Value.Dispose();
+ }
}
protected virtual Stream CreateStream() => this.TestFormat.CreateStream(this.Marker);
diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
index a6197b6009..dc081e0bea 100644
--- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
+++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
@@ -56,6 +56,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -73,5 +76,9 @@
+
+
+
+
diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
index 17f04795dc..fd8b6af591 100644
--- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
+++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
@@ -113,9 +113,15 @@ public partial class UniformUnmanagedMemoryPoolTests
public static readonly bool Is32BitProcess = !Environment.Is64BitProcess;
private static readonly List PressureArrays = new();
- [ConditionalFact(nameof(Is32BitProcess))]
+ [Fact]
public static void GC_Collect_OnHighLoad_TrimsEntirePool()
{
+ if (!Is32BitProcess)
+ {
+ // This test is only relevant for 32-bit processes.
+ return;
+ }
+
RemoteExecutor.Invoke(RunTest).Dispose();
static void RunTest()
diff --git a/tests/ImageSharp.Tests/Numerics/RationalTests.cs b/tests/ImageSharp.Tests/Numerics/RationalTests.cs
index d165bd9d39..f9cefaddda 100644
--- a/tests/ImageSharp.Tests/Numerics/RationalTests.cs
+++ b/tests/ImageSharp.Tests/Numerics/RationalTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Tests;
@@ -14,15 +14,15 @@ public class RationalTests
[Fact]
public void AreEqual()
{
- var r1 = new Rational(3, 2);
- var r2 = new Rational(3, 2);
+ Rational r1 = new(3, 2);
+ Rational r2 = new(3, 2);
Assert.Equal(r1, r2);
Assert.True(r1 == r2);
- var r3 = new Rational(7.55);
- var r4 = new Rational(755, 100);
- var r5 = new Rational(151, 20);
+ Rational r3 = new(7.55);
+ Rational r4 = new(755, 100);
+ Rational r5 = new(151, 20);
Assert.Equal(r3, r4);
Assert.Equal(r4, r5);
@@ -34,20 +34,39 @@ public class RationalTests
[Fact]
public void AreNotEqual()
{
- var first = new Rational(0, 100);
- var second = new Rational(100, 100);
+ Rational first = new(0, 100);
+ Rational second = new(100, 100);
Assert.NotEqual(first, second);
Assert.True(first != second);
}
+ ///
+ /// Tests known out-of-range values.
+ ///
+ /// The input value.
+ /// The expected numerator.
+ /// The expected denominator.
+ [Theory]
+ [InlineData(0, 0, 1)]
+ [InlineData(double.NaN, 0, 0)]
+ [InlineData(double.PositiveInfinity, 1, 0)]
+ [InlineData(double.NegativeInfinity, 1, 0)]
+ public void FromDoubleOutOfRange(double value, uint numerator, uint denominator)
+ {
+ Rational r = Rational.FromDouble(value);
+
+ Assert.Equal(numerator, r.Numerator);
+ Assert.Equal(denominator, r.Denominator);
+ }
+
///
/// Tests whether the Rational constructor correctly assign properties.
///
[Fact]
public void ConstructorAssignsProperties()
{
- var rational = new Rational(7, 55);
+ Rational rational = new(7, 55);
Assert.Equal(7U, rational.Numerator);
Assert.Equal(55U, rational.Denominator);
@@ -71,15 +90,15 @@ public class RationalTests
[Fact]
public void Fraction()
{
- var first = new Rational(1.0 / 1600);
- var second = new Rational(1.0 / 1600, true);
+ Rational first = new(1.0 / 1600);
+ Rational second = new(1.0 / 1600, true);
Assert.False(first.Equals(second));
}
[Fact]
public void ToDouble()
{
- var rational = new Rational(0, 0);
+ Rational rational = new(0, 0);
Assert.Equal(double.NaN, rational.ToDouble());
rational = new Rational(2, 0);
@@ -89,7 +108,7 @@ public class RationalTests
[Fact]
public void ToStringRepresentation()
{
- var rational = new Rational(0, 0);
+ Rational rational = new(0, 0);
Assert.Equal("[ Indeterminate ]", rational.ToString());
rational = new Rational(double.PositiveInfinity);
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs
index 990a97bed0..10811a559e 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs
@@ -27,8 +27,7 @@ public class OilPaintTest
[WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)]
public void FullImage(TestImageProvider provider, int levels, int brushSize)
where TPixel : unmanaged, IPixel
- {
- provider.RunValidatingProcessorTest(
+ => provider.RunValidatingProcessorTest(
x =>
{
x.OilPaint(levels, brushSize);
@@ -36,17 +35,21 @@ public class OilPaintTest
},
ImageComparer.TolerantPercentage(0.01F),
appendPixelTypeToFileName: false);
- }
[Theory]
[WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(OilPaintValues), 100, 100, PixelTypes.Rgba32)]
public void InBox(TestImageProvider provider, int levels, int brushSize)
where TPixel : unmanaged, IPixel
- {
- provider.RunRectangleConstrainedValidatingProcessorTest(
+ => provider.RunRectangleConstrainedValidatingProcessorTest(
(x, rect) => x.OilPaint(levels, brushSize, rect),
$"{levels}-{brushSize}",
ImageComparer.TolerantPercentage(0.01F));
+
+ [Fact]
+ public void Issue2518_PixelComponentOutsideOfRange_ThrowsImageProcessingException()
+ {
+ using Image image = new(10, 10, new RgbaVector(1, 1, 100));
+ Assert.Throws(() => image.Mutate(ctx => ctx.OilPaint()));
}
}
diff --git a/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin b/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin
new file mode 100644
index 0000000000..1fbb392776
Binary files /dev/null and b/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.bin differ
diff --git a/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json b/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json
new file mode 100644
index 0000000000..41a524b893
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestDataWebp/Vp8Residual.json
@@ -0,0 +1 @@
+{"First":1,"Last":15,"CoeffType":0,"Coeffs":[0,-3,-5,1,1,0,0,1,2,0,-1,-1,0,-1,-1,2],"Prob":[{"Probabilities":[{"Probabilities":"gICAgICAgICAgIA="},{"Probabilities":"gICAgICAgICAgIA="},{"Probabilities":"gICAgICAgICAgIA="}]},{"Probabilities":[{"Probabilities":"/Yj+/+TbgICAgIA="},{"Probabilities":"vYHy/+PV/9uAgIA="},{"Probabilities":"an7j/NbR//+AgIA="}]},{"Probabilities":[{"Probabilities":"AWL4/+zi//+AgIA="},{"Probabilities":"tYXu/t3q/5qAgIA="},{"Probabilities":"TobK98a0/9uAgIA="}]},{"Probabilities":[{"Probabilities":"Abn5//P/gICAgIA="},{"Probabilities":"uJb3/+zggICAgIA="},{"Probabilities":"TW7Y/+zmgICAgIA="}]},{"Probabilities":[{"Probabilities":"AWX7//H/gICAgIA="},{"Probabilities":"qovx/OzR//+AgIA="},{"Probabilities":"JXTE8+T///+AgIA="}]},{"Probabilities":[{"Probabilities":"Acz+//X/gICAgIA="},{"Probabilities":"z6D6/+6AgICAgIA="},{"Probabilities":"Zmfn/9OrgICAgIA="}]},{"Probabilities":[{"Probabilities":"AZj8//D/gICAgIA="},{"Probabilities":"sYfz/+rhgICAgIA="},{"Probabilities":"UIHT/8LggICAgIA="}]},{"Probabilities":[{"Probabilities":"AQH/gICAgICAgIA="},{"Probabilities":"9gH/gICAgICAgIA="},{"Probabilities":"/4CAgICAgICAgIA="}]}],"Stats":[{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[3145728,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]},{"Stats":[{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]},{"Stats":[0,0,0,0,0,0,0,0,0,0,0]}]}],"Costs":[{"Costs":[{"Costs":[256,512,1024,1280,1280,1280,1280,1280,1280,1280,1280,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536,1536]},{"Costs":[512,768,1280,1536,1536,1536,1536,1536,1536,1536,1536,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792]},{"Costs":[512,768,1280,1536,1536,1536,1536,1536,1536,1536,1536,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792,1792]}]},{"Costs":[{"Costs":[234,287,2122,2957,3618,4379,4379,4379,4379,4379,4379,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635,4635]},{"Costs":[756,784,1887,2721,3318,3692,3692,4353,4353,4353,4353,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934,5934]},{"Costs":[463,503,1342,2023,2578,2810,2810,4599,4599,4599,4599,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108,5108]}]},{"Costs":[{"Costs":[355,194,1496,2462,3209,3259,3259,5048,5048,5048,5048,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557,5557]},{"Costs":[700,761,1783,2503,3379,3707,3707,3861,3861,3861,3861,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820,5820]},{"Costs":[378,504,1102,1692,2013,2333,2333,2994,2994,2994,2994,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575,4575]}]},{"Costs":[{"Costs":[121,489,1867,2959,4748,4147,4147,4147,4147,4147,4147,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403,4403]},{"Costs":[671,818,2118,3088,3805,4387,4387,4387,4387,4387,4387,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643,4643]},{"Costs":[447,410,1071,2031,2844,3340,3340,3340,3340,3340,3340,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596,3596]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[342,197,1751,2791,4580,4028,4028,4028,4028,4028,4028,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284,4284]},{"Costs":[632,723,1799,2794,3349,3302,3302,5091,5091,5091,5091,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600,5600]},{"Costs":[354,387,893,1672,3461,1944,1944,3733,3733,3733,3733,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242,4242]}]},{"Costs":[{"Costs":[86,596,2405,3568,5357,4688,4688,4688,4688,4688,4688,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944,4944]},{"Costs":[790,991,2421,3640,3640,4693,4693,4693,4693,4693,4693,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949,4949]},{"Costs":[527,423,1330,2054,2314,3558,3558,3558,3558,3558,3558,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814,3814]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[192,343,1900,2902,4691,4176,4176,4176,4176,4176,4176,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432,4432]},{"Costs":[675,739,1866,2791,3528,4132,4132,4132,4132,4132,4132,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388,4388]},{"Costs":[398,477,1157,1633,2350,3355,3355,3355,3355,3355,3355,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611,3611]}]},{"Costs":[{"Costs":[1792,7,2308,2564,2564,2564,2564,2564,2564,2564,2564,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820,2820]},{"Costs":[3008,1223,3524,3780,3780,3780,3780,3780,3780,3780,3780,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036,4036]},{"Costs":[2048,2304,2816,3072,3072,3072,3072,3072,3072,3072,3072,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328,3328]}]}]}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs
index 8aefbe320e..9013d15530 100644
--- a/tests/ImageSharp.Tests/TestFileSystem.cs
+++ b/tests/ImageSharp.Tests/TestFileSystem.cs
@@ -1,6 +1,8 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+#nullable enable
+
namespace SixLabors.ImageSharp.Tests;
///
@@ -8,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests;
///
public class TestFileSystem : ImageSharp.IO.IFileSystem
{
- private readonly Dictionary> fileSystem = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ private readonly Dictionary> fileSystem = new(StringComparer.OrdinalIgnoreCase);
public void AddFile(string path, Func data)
{
@@ -18,35 +20,39 @@ public class TestFileSystem : ImageSharp.IO.IFileSystem
}
}
- public Stream Create(string path)
+ public Stream Create(string path) => this.GetStream(path) ?? File.Create(path);
+
+ public Stream CreateAsynchronous(string path) => this.GetStream(path) ?? File.Open(path, new FileStreamOptions
{
- // if we have injected a fake file use it instead
- lock (this.fileSystem)
- {
- if (this.fileSystem.ContainsKey(path))
- {
- Stream stream = this.fileSystem[path]();
- stream.Position = 0;
- return stream;
- }
- }
+ Mode = FileMode.Create,
+ Access = FileAccess.ReadWrite,
+ Share = FileShare.None,
+ Options = FileOptions.Asynchronous,
+ });
- return File.Create(path);
- }
+ public Stream OpenRead(string path) => this.GetStream(path) ?? File.OpenRead(path);
+
+ public Stream OpenReadAsynchronous(string path) => this.GetStream(path) ?? File.Open(path, new FileStreamOptions
+ {
+ Mode = FileMode.Open,
+ Access = FileAccess.Read,
+ Share = FileShare.Read,
+ Options = FileOptions.Asynchronous,
+ });
- public Stream OpenRead(string path)
+ private Stream? GetStream(string path)
{
// if we have injected a fake file use it instead
lock (this.fileSystem)
{
- if (this.fileSystem.ContainsKey(path))
+ if (this.fileSystem.TryGetValue(path, out Func? streamFactory))
{
- Stream stream = this.fileSystem[path]();
+ Stream stream = streamFactory();
stream.Position = 0;
return stream;
}
}
- return File.OpenRead(path);
+ return null;
}
}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index c21c59a5ca..f6f9ae6dad 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -129,9 +129,15 @@ public static class TestImages
// Issue 2209: https://github.com/SixLabors/ImageSharp/issues/2209
public const string Issue2209IndexedWithTransparency = "Png/issues/Issue_2209.png";
- // Issue 2259: https://github.com/SixLabors/ImageSharp/issues/2259
+ // Issue 2259: https://github.com/SixLabors/ImageSharp/issues/2469
public const string Issue2259 = "Png/issues/Issue_2259.png";
+ // Issue 2259: https://github.com/SixLabors/ImageSharp/issues/2469
+ public const string Issue2469 = "Png/issues/issue_2469.png";
+
+ // Issue 2447: https://github.com/SixLabors/ImageSharp/issues/2447
+ public const string Issue2447 = "Png/issues/issue_2447.png";
+
public static class Bad
{
public const string MissingDataChunk = "Png/xdtn0g01.png";
@@ -295,6 +301,8 @@ public static class TestImages
public const string Issue2315_NotEnoughBytes = "Jpg/issues/issue-2315.jpg";
public const string Issue2334_NotEnoughBytesA = "Jpg/issues/issue-2334-a.jpg";
public const string Issue2334_NotEnoughBytesB = "Jpg/issues/issue-2334-b.jpg";
+ public const string Issue2478_JFXX = "Jpg/issues/issue-2478-jfxx.jpg";
+ public const string HangBadScan = "Jpg/issues/Hang_C438A851.jpg";
public static class Fuzz
{
@@ -418,6 +426,8 @@ public static class TestImages
public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp";
public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp";
+ public const string BlackWhitePalletDataMatrix = "Bmp/bit1datamatrix.bmp";
+
public static readonly string[] BitFields =
{
Rgb32bfdef,
@@ -969,12 +979,14 @@ public static class TestImages
public const string Cmyk = "Tiff/Cmyk.tiff";
public const string Cmyk64BitDeflate = "Tiff/cmyk_deflate_64bit.tiff";
+ public const string CmykLzwPredictor = "Tiff/Cmyk-lzw-predictor.tiff";
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";
public const string Issues2123 = "Tiff/Issues/Issue2123.tiff";
public const string Issues2149 = "Tiff/Issues/Group4CompressionWithStrips.tiff";
public const string Issues2255 = "Tiff/Issues/Issue2255.png";
+ public const string Issues2435 = "Tiff/Issues/Issue2435.tiff";
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";
@@ -1039,5 +1051,18 @@ public static class TestImages
public const string RgbPlain = "Pbm/rgb_plain.ppm";
public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm";
public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm";
+ public const string Issue2477 = "Pbm/issue2477.pbm";
+ }
+
+ public static class Qoi
+ {
+ public const string Dice = "Qoi/dice.qoi";
+ public const string EdgeCase = "Qoi/edgecase.qoi";
+ public const string Kodim10 = "Qoi/kodim10.qoi";
+ public const string Kodim23 = "Qoi/kodim23.qoi";
+ public const string QoiLogo = "Qoi/qoi_logo.qoi";
+ public const string TestCard = "Qoi/testcard.qoi";
+ public const string TestCardRGBA = "Qoi/testcard_rgba.qoi";
+ public const string Wikipedia008 = "Qoi/wikipedia_008.qoi";
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs b/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs
index 732948b8e0..7b519531ab 100644
--- a/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs
@@ -13,5 +13,9 @@ internal class SingleStreamFileSystem : IFileSystem
Stream IFileSystem.Create(string path) => this.stream;
+ Stream IFileSystem.CreateAsynchronous(string path) => this.stream;
+
Stream IFileSystem.OpenRead(string path) => this.stream;
+
+ Stream IFileSystem.OpenReadAsynchronous(string path) => this.stream;
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
index b91b5631bd..6c6f300d02 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
@@ -62,7 +63,8 @@ public static partial class TestEnvironment
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new WebpConfigurationModule(),
- new TiffConfigurationModule());
+ new TiffConfigurationModule(),
+ new QoiConfigurationModule());
IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration();
IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder();
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png
index 79a43ed87a..f226b166e4 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4d88eb2e50ca9dbed0e8dfe4ad278cd88ddb9d3408b30d9dfe59102b167f570b
-size 262887
+oid sha256:98115a7087aced0c28cefa32a57bc72be245886cabeefc4ff7faf7984236218c
+size 271226
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png
index f16ff0ef75..5961b0384d 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:97cfbef27319988b67aeac87d469d044edd925c90e4774170465f51eed85c16a
-size 42915
+oid sha256:58c03e354b108033873e2a4c0b043ce15919c4d0630e6ca72ff70b89cbedb979
+size 44239
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png
index 05d26b6475..a2bbce465e 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3a799b69938507e3fd2a74ffa7c6c6ad6574acb25861a0a50cb8361520d468de
-size 41809
+oid sha256:f987f4d270568facefc11eee7f81dd156af56c26b69fe3a6d2d2e9818652befa
+size 43116
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png
index b437c0d034..727a45d0b1 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9932db58eeb966cd293b1b7a375e9c1b17b6d09153c679ebf03d42a08d2ce9b3
-size 43332
+oid sha256:ebdad83936e50bbb00fd74b7dd7d2f5a480bb7347aa3d151e7827107cd279bac
+size 44441
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png
index 9e97e5f96c..5f9b599b7d 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:67ebf42bc82483d1778254d95a376230437611dce91c80f8ecda608de56bffe7
-size 43108
+oid sha256:ccdf5937c30999e3b09071200de2e1db63b606ad9cbf6f7677a7499fb0b52963
+size 44252
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png
index b84521842c..b50fce4dc1 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8d5cdda990ac146a7580f58cc2bcab72f903dde564a394de7df4cc37e6dcf2dd
-size 43906
+oid sha256:baf70b732646d7c6cec60cfbe569ec673418dfb2dd0b5937bccfb91d9821d586
+size 45053
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png
index 436c676926..dfdb4f642f 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c11e6c197bd1c227ae8f4af7e8c232cfe75db6929ab12bddf5e6554fbaed3f01
-size 50716
+oid sha256:f6a1eae610ed730e4cec41693829929ba8db674886c2bd558f1b8893d2b76802
+size 51201
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png
index 6e1ad33117..c37ea9dcaf 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:69ff9654eb61f2bfdd44fb25aff959c5b831015e283cc91a90e3abf6f681dc88
-size 52429
+oid sha256:ba674e0236c2e146c64a7f3e224c702030769304cd0fd624d1989536da341659
+size 52814
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png
index a257ccd615..d1ccc680b6 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6ee945ac5120e4198d1f94e6467cc0f77c90869bf5a09942e7720dddcfdfbe07
-size 51262
+oid sha256:316231c8d837f864cf62dcc79fdce698dc8c45c0327372de42c2b89eac1d9f81
+size 51851
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png
index d8cb415022..35ab5b75b8 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1b023505175ae39a93fa55c85aa31466f0aca76fab0ee54f9667648b91f9aeb9
-size 50789
+oid sha256:b58144146585f50960dfd6ac5dc3f52238160287ae5f9b18c6796962cc3d2fd2
+size 51550
diff --git a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png
index d6be5125f8..fde570043d 100644
--- a/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png
+++ b/tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6b18e8b80035a3c5985ebedab5eaf1b0e580d26dd2a8167e687e7b3dd6536751
-size 51922
+oid sha256:6515be764d7026a87cfeea2d58344c404e4f15908139a25f413d51cc7cc61a0c
+size 52216
diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png
new file mode 100644
index 0000000000..6bf7bb19b0
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2012789669110c08a00d37add7f53967b902bd617c90f85d7e90b13a32a0a429
+size 354
diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png
new file mode 100644
index 0000000000..232184c4c2
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f628327efbf1e530d32dc092f2ab361de5ab35fe78db6b5e0274c71f1d170496
+size 363
diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png
new file mode 100644
index 0000000000..fe4e44fbf8
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:40fc8f14b8f9e98fd73855f3dfada39062cc1aff874b3389133a55eb2e968f66
+size 354
diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_issue2477.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_issue2477.png
new file mode 100644
index 0000000000..e8a70e6b41
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_issue2477.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:670bc844ba878afa0f03574dea23ab774ac0cc5aa371d0f4b4dff7da4d32f916
+size 2912
diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_ScalarResizeKernel_splash_150_150.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_ScalarResizeKernel_splash_150_150.png
new file mode 100644
index 0000000000..62251f931b
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_ScalarResizeKernel_splash_150_150.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:11e8aa44e32ae133914c91cc32a58ecdba1a107d36a0ca252e0e088053e57be1
+size 28129
diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png
new file mode 100644
index 0000000000..48e4261f1e
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ef5a85b2adde25b5f343a18420fe787f5e159029a361a15ef2d6322eb7bb81fb
+size 944597
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_dice.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_dice.png
new file mode 100644
index 0000000000..44b2fa1187
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_dice.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8e4a5cf4e80ed1e1106eceb3e873aecf7b8e0022dfe39aa4c0c64ffc41091f09
+size 243458
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_edgecase.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_edgecase.png
new file mode 100644
index 0000000000..d499d74c31
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_edgecase.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:12c966382b318c58578e3823ac066c597ce1e16ce7c2315b0f9d66451803a082
+size 1245
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim10.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim10.png
new file mode 100644
index 0000000000..c038bcdcf2
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim10.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca18bd41b7d6db902e86c7a1be32ceb0989aaec0bf9fa94ca599887970b83e63
+size 598510
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim23.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim23.png
new file mode 100644
index 0000000000..1742ec80f3
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_kodim23.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f6c7a229a652bfcaba998e713e169072475bea9bba35374be9219eb19c6ab42b
+size 562295
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_qoi_logo.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_qoi_logo.png
new file mode 100644
index 0000000000..28e4bca77d
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_qoi_logo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:593549012cf9573c457c4de9161c347f1ae81d80c057ea70b89fbb197bdd028f
+size 16953
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard.png
new file mode 100644
index 0000000000..3ba2c266ca
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4ad1df5a4549a4860e00fbb53328208d4458e1961ae2fac290278c612432d1e7
+size 12299
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard_rgba.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard_rgba.png
new file mode 100644
index 0000000000..4b2c4c0c0d
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_testcard_rgba.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ed62e82f1fed2bf16569298a61f792706a1b61e99026acefcbf8aeb0da6f6e08
+size 16075
diff --git a/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_wikipedia_008.png b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_wikipedia_008.png
new file mode 100644
index 0000000000..56b87a98f6
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/QoiDecoderTests/Decode_Rgba32_wikipedia_008.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ed7705c6ccb440f6bff77b0b9ac8275576d3f1c1fa4ecaa83ff80a72359e6f2f
+size 1376202
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png
index fccbe25877..4b1e9fed20 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19
-size 13097
+oid sha256:033bb9e0ce89ffe4abda4d409af5741958d4035f9c9824c28d7598d72c4db96f
+size 13818
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png
index 8d0c3a5d99..cdf5f1a963 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3c7d3da0ced1c66c6351d530565a190cfc1fdb7f3b7b05d39844f61fb87871ad
-size 13758
+oid sha256:9c6617ebf10f2fe1608fbc2a3c75f1a86ff4e3835b5d3fd7fcc2e5d0a4e5bbb1
+size 14380
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png
index cffaa87b48..2ad42755c6 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2a6bb9a04f0663eb8a95d6d46c72557078de35ac935499d5ec4ab591d7f59eb9
-size 13940
+oid sha256:5bb52f047f410e2b0bdcd8d186043f0d3b03835f39007775608fb05365ac9a20
+size 14616
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png
index fccbe25877..4b1e9fed20 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19
-size 13097
+oid sha256:033bb9e0ce89ffe4abda4d409af5741958d4035f9c9824c28d7598d72c4db96f
+size 13818
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png
index 8ea07490e3..aabb936bd8 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f0facae77f6022c92cdaaa7f27efb424962933c0e86ec4e8a7d62237a0f58d03
-size 13919
+oid sha256:47e0924ddbf191dca8932eb46b9c533d0983b9fbce956026b392d5fe589fb90a
+size 14630
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png
index e7fe3bc772..0fe9cd8672 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ec99338895bdada5cabe504afdcb0c0c95d8951e4404d31615a406b9956995c0
-size 14154
+oid sha256:4ede7ffc5d07a09c7c5706e8e2554897d29acaabf71701191b0f689f2c22ae71
+size 17826
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png
index 853e368e3e..1e8cd88ffe 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9699207803467b8718a719c7581e1ed6bf0c923a5adaf325aea8358d274fece5
-size 18334
+oid sha256:f91c7f1f687c73dd909b629e40f703740270042507c47aa3834ccd38bf289dc3
+size 19394
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png
index 5ace2a5059..a9f7d76f80 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0e3acfa5b7c6ef3bec68b5fa8db91b2e6160e01d1f952a055831cea2f0a58b0f
-size 18675
+oid sha256:2b836ab4e0f1c95fe5d553bf5bca37eac402ba431268d25e4641e86c952f5fdd
+size 19802
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png
index fccbe25877..4b1e9fed20 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19
-size 13097
+oid sha256:033bb9e0ce89ffe4abda4d409af5741958d4035f9c9824c28d7598d72c4db96f
+size 13818
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png
index e4e4e10942..958eee6ad7 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fc1c1b5d0d0abec9b52ae7a83946a46020d2394a5f49f42e2ddb50fac988e974
-size 18874
+oid sha256:00bcd2ac107f45cb31efee44de275dd597eae6d2d4fda71c397416ff5f2f0914
+size 20175
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png
index d8e5bc5798..881ee4e58a 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2eac7954110e82c7c9cb1c0d3734467b7e46745ea19b2fd10d0af7df0aad552c
-size 9007
+oid sha256:b04b71b0a4718e5b3f91c33c23ef792fc81a67c9fef7b9e4d80bdb9dce3539dd
+size 9107
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png
index 2f0961df37..6804454813 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:51b06fc436e322ff9fc9e367b8117eb1178e112eb90fbd41a87847ab64a24136
-size 8801
+oid sha256:e0125454d2c3859d8457202856e21591ab96f61e2a29c3f017af29bf03961c48
+size 8883
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png
index 0858c8e20c..52ba79e98b 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e46c5f17ef76f11ca1dfe70dd4b38858de049832c26add1e9f987f87319a3491
-size 11029
+oid sha256:213ebd9fc7a190ad7226b487387edb9452284193cee4c4720448d7e19ef38e76
+size 11149
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png
index b8f2940f5c..fc26d1b766 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3527a0577720e7e8abf36b534540e72d17854d7b3b7d70cf3cdb519318e9e3c8
-size 7702
+oid sha256:43b21e948795abb52ebbbf94e785542e55488cc7f17996e2b92404ca8ad1a7cb
+size 7844
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png
index c6818e906c..6f67736dd6 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e73014c6698526f3341e1f6001938bf5c60501bd6114451903a654c43c5f1997
-size 11719
+oid sha256:0bb8f730a43a8f58e4269905014461bab8dc8b47387ec86a84c1064b1cdabe14
+size 11813
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png
index b120b7fe98..22ce841b48 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:420d8ff32aa8ffa789e0c5dd00151856a016bc4f83ad035fdb4a8a22c338e247
-size 8952
+oid sha256:7500be583beb22759dec7e5bbb2a8d2230054366bfbd0e39bf10fdc8af63eb58
+size 8925
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png
index e58dac830f..2457c12e91 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:93f3be15cb660c7c74c0de12d459c390c5f3c950d09dc4bcf617f5093e2b818b
-size 8606
+oid sha256:c48da7aad9c0aad576293d7085f9ddb2ea90a76d53bea54cbe0cb7aef71c7bb4
+size 9136
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png
index b6bb89b9ec..33ec41713e 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a035a0b97ac471500a9dbead47a0d13deb449136980b89795b671b3e14481c9e
-size 9716
+oid sha256:72e6c327e98d9929a4d850905b1471fcd1191088feb38b028e4240e9f93b4996
+size 9827
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png
index b8f2940f5c..fc26d1b766 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3527a0577720e7e8abf36b534540e72d17854d7b3b7d70cf3cdb519318e9e3c8
-size 7702
+oid sha256:43b21e948795abb52ebbbf94e785542e55488cc7f17996e2b92404ca8ad1a7cb
+size 7844
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png
index f6bae9649e..ea35c328c4 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fac9fc2316ccf7a464e93d0406acdf37d5aac7f76f54c87454fa41b13c8224fc
-size 9731
+oid sha256:70f8fc84d6e578822582ebee5888514dcd70c8d0da1a920f35e63a1617d1e92c
+size 9841
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png
index beb4248eda..e2b742ca45 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c738cea16a714bdfa54cfcc213102d53ebe3aee576390902d352f04be65edac2
-size 11166
+oid sha256:ff4d6e3a7c8e69db0d5a88b7f1c91621692962bdbcedd6b8af1311506243bcde
+size 11259
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png
index 7d271c8065..61a28a9cfd 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:42b44354480a1d2c869f541e6f3ed9feec15fb04ad32eb2a21b7d65290eeec54
-size 11972
+oid sha256:520910b75cae834b9844ce896ad9ab6d0b6c33c44c7bd822ce63752aaa2a8c5a
+size 12122
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png
index 6ef7e65498..bfaf9e30ab 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d7d62b46acff22858a1621656ddaa97c3610a7f13df9c5d77747b7364620b174
-size 12772
+oid sha256:8e1fb4bb5d1591ac6bb8a4fb2504e2e836578c9c42d8286061cf4f57f0b7f97d
+size 12876
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png
index 0062fbcb98..78576c467e 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9e532758291dd3d18b5b81c1d788db7854b322a633557f3ee273bb9d68c465ba
-size 9582
+oid sha256:3f5babf5f6ae746a9b9f131ecf990f037c8e8bdb257bdbb7f6127460c9b8f98d
+size 10507
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png
index e20bd0e4b0..71d771fe36 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_ErrorDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:581febc9878288785ae82b23f2946dc0c506ae86fba32bb02ba5e69cf1c8cda1
-size 14069
+oid sha256:56a9088fdca14b82fa3ac3ecfb67b6b8e97e8d69b89907c8408e17861e8609e0
+size 14195
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png
index 3c2d6529fb..a68c99d4d9 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:12f7bacf0402f821e3c80f65c29218bc1f1334392edc463b617cf711667db722
-size 12381
+oid sha256:7d4ef129d8846e6802997c0ace90abdc7412d232e9a31fc53f25bb9f32de08f2
+size 12665
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png
index 07790191db..992ae04257 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:436d168a3501da20c327cb3d2909cdd465585ee3f76a2534e37a36771e10115e
-size 12596
+oid sha256:69b5771a313aa777341d1460db94a7b3cc74b69c44f6c9d1a1b5b3734936e795
+size 12791
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png
index 49a4514226..7865b70d7a 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c952f81377c83b2255c427d0911b898e500d163d870de778b69778a9ab8c8278
-size 12459
+oid sha256:a56f72c5a12e6d216a32404c996e72721b4cf350d82071a43ebe44190adef94a
+size 12895
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png
index 0062fbcb98..78576c467e 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9e532758291dd3d18b5b81c1d788db7854b322a633557f3ee273bb9d68c465ba
-size 9582
+oid sha256:3f5babf5f6ae746a9b9f131ecf990f037c8e8bdb257bdbb7f6127460c9b8f98d
+size 10507
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png
index 394f8f85b4..140af0ed89 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2b542c86ea4fef3a37e89c1087dddafeeccf523e7c0721743f34d35da5e0653e
-size 13116
+oid sha256:39f85cb564a257381656fd969acaeda04016e09380133f772eb029e332aeaa95
+size 13482
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png
index 296267b8cf..1a53f430c6 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:147b7ebbe92f2379d513a44214a8383ed96a94b92f9d80cb3c5944e5e32e94bc
-size 13097
+oid sha256:9d528ea896a2e56e0b8967d5ce3486078cbd7bf29e5f0d43a58ef21100147915
+size 13818
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png
index e710de72c8..90823958c7 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5b55add6cd3dc0e130f399a6932ba279aa29dc72579ee575df88e0faf76a3835
-size 13073
+oid sha256:4e96967a2a6362f42026ab2e2cdd82437e573c16a52c5631f7495cb30615441d
+size 13841
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png
index fb03fbbf9c..f0270cd0ac 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ed5c1745e2ef33654023ed0a8bfabe5a75d46186aa5c42df54ac1a9506dcf632
-size 13431
+oid sha256:d5f9e9e4f7f48681d5b240d6df6ae282c6b9e896eacc1ad73f2a097975fa8d29
+size 14060
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png
index 296267b8cf..1a53f430c6 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:147b7ebbe92f2379d513a44214a8383ed96a94b92f9d80cb3c5944e5e32e94bc
-size 13097
+oid sha256:9d528ea896a2e56e0b8967d5ce3486078cbd7bf29e5f0d43a58ef21100147915
+size 13818
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png
index 28b9e8811d..636f8299aa 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:945c33feb2f3408b54e4574781eee3c2868885af25acd9172d420360e505b54a
-size 13463
+oid sha256:97d6923c3342d600a9a2ab3fa713136b2649b74364d6e90a905de607838e0cb7
+size 14319
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png
index 554f587743..f287f604e0 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3edb6672168fc58a2bb6766d48a0883aa35fdc6873d2f4b9f26d3b5fa6cb46dd
-size 15574
+oid sha256:66399a4ba262bdacdc5966fa9fc6f8f6bfa3f8c70db889363e9bbd5778dc1ecf
+size 16081
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png
index fc6da7bbb1..f6a36e2035 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.5.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9fd79ec840f8bd82b41d93987187531187a4bd957d7f0d497a86fa61de52cec7
-size 16733
+oid sha256:5953a12a2fa67f9fe17485c62d156bb7f80a4bcf4124c123db4440c2559e0e69
+size 17002
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png
index 36015f6638..03519e0322 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:360121a75c96434daa57f2b996e9776cc1efdf25aa3f7e926abd6d04c9ee4184
-size 17355
+oid sha256:efc08c6969344de404692c2b367130e3f736442c0722067a9105f036a9e4511e
+size 17616
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png
index 296267b8cf..1a53f430c6 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:147b7ebbe92f2379d513a44214a8383ed96a94b92f9d80cb3c5944e5e32e94bc
-size 13097
+oid sha256:9d528ea896a2e56e0b8967d5ce3486078cbd7bf29e5f0d43a58ef21100147915
+size 13818
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png
index 777be644a4..c4c8d7aac3 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:97d3b5d804c50da0d9c5db7278b16bb807e246dbd083c8c62ec7d4d7a65a1b45
-size 18070
+oid sha256:455488369cd943f3c3a0b2c402dbb17d6fc9c384b5f26dbc049634ad4bdab73f
+size 18259
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png
index 8f4f0e32e6..ce6858e2d8 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7f7ce90fb4dec4b890eb8bfd182e009b2769104ab2f14e926381c4949d6f7453
-size 82121
+oid sha256:2a65ffa4c6b8488f52892306052337e17a4e58d28fa43f18009e6d1f997962de
+size 83060
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png
index a0a5cc565d..651ed382fe 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2430b92bc20b2c3d142b5f84ae9fd62856fc4c717b0b226c2e096d725883d41f
-size 54154
+oid sha256:1f69909cdc4b65548834e30caf44a31b7e5d41be1db05cb43bd8277613f327ed
+size 55263
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png
index 4b7a06f302..501074f232 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4cd9433cdab37510cf6d98ce5838a69675359982376f7ef5c9e716c49772af74
-size 79370
+oid sha256:ff7d35f1ce04d8f2b6cee41c951487bd24e18f923dc84c77a7944ec3aab61540
+size 80844
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png
index 5d0c82e058..08ddd2deff 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5
-size 61183
+oid sha256:48ac9eea396e010b1c110413a6861b1708f3928243271ea78f390e64dfe11737
+size 62249
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png
index 6fd875a6fd..da68bff3ce 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a5ad9cb26866b35f6ad8c0ae054c7172a15b2fb2512bd123af3c0e5685c30410
-size 32766
+oid sha256:a4786eb2c04be00302996f3ad65987f54fe5d80ded438fdcccf7b9bfe9520dbb
+size 33930
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png
index 9e97e5f96c..5f9b599b7d 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WebSafePaletteQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:67ebf42bc82483d1778254d95a376230437611dce91c80f8ecda608de56bffe7
-size 43108
+oid sha256:ccdf5937c30999e3b09071200de2e1db63b606ad9cbf6f7677a7499fb0b52963
+size 44252
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png
index cfcafba1a7..60e5f18dc0 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b5271fba5dcee48982ccad321f987a67d6663dabc01d380eb0cafc178251bc00
-size 33971
+oid sha256:898c8524d6fcad8498a22dd97956265302458d43f8d3f93846c2268d7b47fb73
+size 35351
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png
index 2fc55fb4dd..cf7cba1811 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1ba613bc2cf88dfb357e88671464272ab4279667b8c776b8b9db913161b7f450
-size 33060
+oid sha256:5f37abc44b703b6feac3a2a950df7f40a8a67fe64fa25c19af59862e5272f0ff
+size 34311
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png
index e3e48a17a6..8ddd2cf001 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:242379eee61c3d82f10e8b36db0567749443f91a6e13e766cc1ee3a3eeff7e2c
-size 43006
+oid sha256:1cb4518f6b9e1ed1409b0f4e87c17ae990cff2725c650d69941cc76ba90b2f7b
+size 44672
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png
index 50d141aa1d..e2fc05dd92 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fe72f6268d445f204afcea4723624398ff49e479e8b608843cf287dfb94ebe4e
-size 101257
+oid sha256:16a592718ba5aa15f79ef4864cba75fae5a7cc5e13ca56eeab5fd13a0e5347de
+size 102049
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png
index e555a2cbd9..fb44b61f9e 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c29c21979beeb7f659979893d05d1da15602a8fbc4a61309cd6380b296d69367
-size 83563
+oid sha256:86ebb09209d4bfbe266c633df1c7062755cd413f47d0a96a05bcf003a02cb12b
+size 84428
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png
index 54cacf5a10..b4ff7af8d8 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:49e072dc73ba96dffa021b3e9bbf169102bd9ae7b9d4ed0a69b55178f1592ae5
-size 97415
+oid sha256:779521e132b4ee6b3ac7a9186fca3250c6d2ace7e55f4f6c9601fe5393d9eb10
+size 99561
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png
index bbe5e4a20a..8b5956b1ac 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8c6041ecc220ee8cd576aff06871bc1f3b7363dffe334bbab83344c5b96cbde3
-size 94511
+oid sha256:a6d0b12e19b980fd558a4920ec18620a9bdb00bd1379a20719ff0ee92c6887a1
+size 96007
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png
index a09d04c793..e1122365e9 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:912de82dc98a8dd72ffc5549125c397379a859a23ffe48f01e4f1c5a28ff1d18
-size 77029
+oid sha256:82c34b115c081a1f3723540b9a273724353930c53006dd6daea576a227271e01
+size 78599
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png
index 44139c4e00..30cfcf2dea 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bedb363c412c4c387fabe4d65ca769079376f4cc56a3bfdd767f0ae8441b3dfb
-size 92003
+oid sha256:6fbe71277a196e5c57fba7caba5c0a1b2bff2da9eadbd7bab1ae1853fab2dd93
+size 92581
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png
index a41b9989f8..d79f324a77 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295
-size 60377
+oid sha256:b1061a363744661febdd051ef2f6839bfe288eb83f7ebf281fb06717fbe6703a
+size 60739
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png
index 9cbb203986..03baa702a7 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:eee438f7cbe6615bab0df73689f6924ea153da28eaf1f4c0c22076f24f18085d
-size 46476
+oid sha256:cacd80b681c086310d3be7d90575da357e6ab0a62e24227a1e7e3ae4cab1de2d
+size 46905
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png
index d8cb415022..35ab5b75b8 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1b023505175ae39a93fa55c85aa31466f0aca76fab0ee54f9667648b91f9aeb9
-size 50789
+oid sha256:b58144146585f50960dfd6ac5dc3f52238160287ae5f9b18c6796962cc3d2fd2
+size 51550
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png
index d7c0cbc019..dfcc0603dc 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c175f0db79d3ac74043dce3fe57d5c15c6ca38c954c008baf5fa917d3b9d4e0e
-size 67374
+oid sha256:d54a2b762a16c76ad52919708d0126ae63958e9d900e3870cced695540e16192
+size 68366
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png
index 529557d9d8..c1c66f8409 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7c76f0df12da8eac1fefb6ba9c0c89f5c8a7bcfbff442a4ebd763f1a4b359637
-size 63046
+oid sha256:87450152fdccfaebb55e22040206cb246bc59b61461fd8b6ca6e099256fa0f1d
+size 63839
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png
index efbb6a013b..f79a145a6e 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f2636953295972ede173dbfaf3b67f7cb91f1c3f4ccc79f70e078bd94af9422d
-size 68579
+oid sha256:7136f92f3fd1927e98204b1140ca36a901dcd9b67c17f7080413588c2e2dcc28
+size 69579
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png
index ca83b5de0d..79cec59e5f 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:68e401e5f9aeb4c5fa0b8413871436f1eb33fe5eb82026f2ad5665169a13d0de
-size 112784
+oid sha256:ab5972eaa6b008b9768f3af4c61957db3d6da41cdf52696c05ecd3f2efaf3d5f
+size 113964
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png
index 485f36f456..159e284c59 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7a59de505b2f7f0f14a3bc513f477f6ae6fd3a72ff7bc7c628a4efba18fed565
-size 108009
+oid sha256:46c6b9eb83c6d4ffafc2163b0e0ccd5aa24ac56bb65e8cf5f02b80319cf29e4b
+size 108931
diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png
index c29d9ec100..c11f7ac792 100644
--- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png
+++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a65928b17616922155b030737af67de806c195bd993752a7d5e17ec7e94150fc
-size 113919
+oid sha256:f54b00e54e8786c9c9ff8e8cf7e42111e06e5dc5d2e69ee2c8c8be2353030e43
+size 114680
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-lzw-predictor.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-lzw-predictor.png
new file mode 100644
index 0000000000..00fe4de822
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-lzw-predictor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:db076491d7afc78cb5de99cb1e7a9c53f891157bf064994c60d453aec75b9c90
+size 512
diff --git a/tests/Images/Input/Bmp/bit1datamatrix.bmp b/tests/Images/Input/Bmp/bit1datamatrix.bmp
new file mode 100644
index 0000000000..ee5535d112
--- /dev/null
+++ b/tests/Images/Input/Bmp/bit1datamatrix.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b2288e2a059b15c7855eb141c05e3ce69431e7c3ddef851033f7fd9ca39a2d4
+size 102
diff --git a/tests/Images/Input/Jpg/issues/Hang_C438A851.jpg b/tests/Images/Input/Jpg/issues/Hang_C438A851.jpg
new file mode 100644
index 0000000000..97ab9ad0fb
--- /dev/null
+++ b/tests/Images/Input/Jpg/issues/Hang_C438A851.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:580760756f2e7e3ed0752a4ec53d6b6786a4f005606f3a50878f732b3b2a1bcb
+size 413
diff --git a/tests/Images/Input/Jpg/issues/issue-2478-jfxx.jpg b/tests/Images/Input/Jpg/issues/issue-2478-jfxx.jpg
new file mode 100644
index 0000000000..e65433c696
--- /dev/null
+++ b/tests/Images/Input/Jpg/issues/issue-2478-jfxx.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6bd5d14cbbead348404511801d7a2bacab19174e9f4063b5d2cec96f28fd578e
+size 300170
diff --git a/tests/Images/Input/Pbm/issue2477.pbm b/tests/Images/Input/Pbm/issue2477.pbm
new file mode 100644
index 0000000000..0123c65ee2
--- /dev/null
+++ b/tests/Images/Input/Pbm/issue2477.pbm
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d625635f7be760fbea935056c0f6d046832dd74bba33a1597b52ab3dfe0c5e4e
+size 4956
diff --git a/tests/Images/Input/Png/issues/issue_2447.png b/tests/Images/Input/Png/issues/issue_2447.png
new file mode 100644
index 0000000000..3b79487c59
--- /dev/null
+++ b/tests/Images/Input/Png/issues/issue_2447.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:52f7e55f812db926d95ac1ab0c3235fbaca53331b99f73e65f3c1c2094503e20
+size 15824
diff --git a/tests/Images/Input/Png/issues/issue_2469.png b/tests/Images/Input/Png/issues/issue_2469.png
new file mode 100644
index 0000000000..984df7d9d9
--- /dev/null
+++ b/tests/Images/Input/Png/issues/issue_2469.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2be3ee00b78810d74f20a8b1c9ea3d68d8ec0560506dcccc8acda741d7c1251a
+size 2392860
diff --git a/tests/Images/Input/Qoi/dice.qoi b/tests/Images/Input/Qoi/dice.qoi
new file mode 100644
index 0000000000..0b1399a25b
--- /dev/null
+++ b/tests/Images/Input/Qoi/dice.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b05a622813eff15ce64f33ab76eee3f9d144f5cf24386e13ddf17c27f6310a01
+size 519653
diff --git a/tests/Images/Input/Qoi/edgecase.qoi b/tests/Images/Input/Qoi/edgecase.qoi
new file mode 100644
index 0000000000..8ce4eb1fdc
--- /dev/null
+++ b/tests/Images/Input/Qoi/edgecase.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3cae50b533fbc796171a0763c29a576eaac475d04b6a95fe46b02d440f609e11
+size 2114
diff --git a/tests/Images/Input/Qoi/kodim10.qoi b/tests/Images/Input/Qoi/kodim10.qoi
new file mode 100644
index 0000000000..c0e3dab4ca
--- /dev/null
+++ b/tests/Images/Input/Qoi/kodim10.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e330cc81299a2641386f32bdf4b7070b8d5f8f2f76d899ced389b5a1469e65b0
+size 652383
diff --git a/tests/Images/Input/Qoi/kodim23.qoi b/tests/Images/Input/Qoi/kodim23.qoi
new file mode 100644
index 0000000000..d1c3fb59c1
--- /dev/null
+++ b/tests/Images/Input/Qoi/kodim23.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d225e987dc07262be2acee5dee164b5f48d3a49dd0e03f426b3111b52f265548
+size 675251
diff --git a/tests/Images/Input/Qoi/qoi_logo.qoi b/tests/Images/Input/Qoi/qoi_logo.qoi
new file mode 100644
index 0000000000..74624947ed
--- /dev/null
+++ b/tests/Images/Input/Qoi/qoi_logo.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e6519746939c2b6bc6776a65ce87b1dbd769069c2d2c11295453e9f35160ba57
+size 16488
diff --git a/tests/Images/Input/Qoi/testcard.qoi b/tests/Images/Input/Qoi/testcard.qoi
new file mode 100644
index 0000000000..4e283b24ee
--- /dev/null
+++ b/tests/Images/Input/Qoi/testcard.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:de309646439d2e49c51d9921eb1faff9af4cb33f0019a24ccb57dce1ef00dbab
+size 21857
diff --git a/tests/Images/Input/Qoi/testcard_rgba.qoi b/tests/Images/Input/Qoi/testcard_rgba.qoi
new file mode 100644
index 0000000000..7f0de939e5
--- /dev/null
+++ b/tests/Images/Input/Qoi/testcard_rgba.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b284ed810a892bca34e89a956b7f8bf21afae4826197a8f3eaef90e470e2149e
+size 24167
diff --git a/tests/Images/Input/Qoi/wikipedia_008.qoi b/tests/Images/Input/Qoi/wikipedia_008.qoi
new file mode 100644
index 0000000000..2d84a0ad1e
--- /dev/null
+++ b/tests/Images/Input/Qoi/wikipedia_008.qoi
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a289c12cd96cc3ff65fcafa1a6d55c5cace0095a45bc570ca1a4d8b79a20b4df
+size 1521134
diff --git a/tests/Images/Input/Tiff/Cmyk-lzw-predictor.tiff b/tests/Images/Input/Tiff/Cmyk-lzw-predictor.tiff
new file mode 100644
index 0000000000..c5056a9282
--- /dev/null
+++ b/tests/Images/Input/Tiff/Cmyk-lzw-predictor.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d322e42dd61c528e91ba9d16310248a4b9a77094a22761dcb9e6f132fc16fe1b
+size 1080
diff --git a/tests/Images/Input/Tiff/Issues/Issue2435.tiff b/tests/Images/Input/Tiff/Issues/Issue2435.tiff
new file mode 100644
index 0000000000..2afc20a558
--- /dev/null
+++ b/tests/Images/Input/Tiff/Issues/Issue2435.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e5c90d6d90f1cf090562d7d70df858b83513e5d7d78fb7b7c4d7992cb620030e
+size 258540