diff --git a/ImageSharp.sln b/ImageSharp.sln
index 3ea3160a7..5f4fbd0da 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -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/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs b/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs
index 949e383d9..9133f88b9 100644
--- a/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiColorSpace.cs
@@ -13,10 +13,10 @@ public enum QoiColorSpace
///
/// sRGB color space with linear alpha value
///
- SRGB_WITH_LINEAR_ALPHA,
+ SrgbWithLinearAlpha,
///
/// All the values in the color space are linear
///
- ALL_CHANNELS_LINEAR
+ AllChannelsLinear
}
diff --git a/src/ImageSharp/Formats/Qoi/QoiConstants.cs b/src/ImageSharp/Formats/Qoi/QoiConstants.cs
index afad6d3bc..acebb13a7 100644
--- a/src/ImageSharp/Formats/Qoi/QoiConstants.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiConstants.cs
@@ -7,7 +7,6 @@ namespace SixLabors.ImageSharp.Formats.Qoi;
internal static class QoiConstants
{
-
///
/// Gets the bytes that indicates the image is QOI
///
@@ -15,7 +14,7 @@ internal static class QoiConstants
///
/// The list of mimetypes that equate to a QOI.
- /// See
+ /// See https://github.com/phoboslab/qoi/issues/167
///
public static readonly IEnumerable MimeTypes = new[] { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoder.cs b/src/ImageSharp/Formats/Qoi/QoiDecoder.cs
index b36ca77f8..175291de5 100644
--- a/src/ImageSharp/Formats/Qoi/QoiDecoder.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiDecoder.cs
@@ -1,9 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats.Png;
+
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));
@@ -22,6 +30,6 @@ internal class QoiDecoder : ImageDecoder
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
- throw new NotImplementedException();
+ 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
index 717ea5165..1594165ba 100644
--- a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
@@ -49,30 +49,31 @@ internal class QoiDecoderCore : IImageDecoderInternals
{
ImageMetadata metadata = new();
- byte[] widthBytes, heightBytes;
- byte[] magicBytes = widthBytes = heightBytes = Array.Empty();
+ byte[] magicBytes = new byte[4], widthBytes = new byte[4], heightBytes = new byte[4];
// Read magic bytes
- int read = stream.Read(magicBytes, 0, 4);
- if (read != 4 || !magicBytes.Equals(QoiConstants.Magic.ToArray()))
+ int read = stream.Read(magicBytes);
+ if (read != 4 || !magicBytes.SequenceEqual(QoiConstants.Magic.ToArray()))
{
throw new InvalidImageContentException("The image is not a QOI image");
}
// If it's a qoi image, read the rest of properties
- read = stream.Read(widthBytes, 0, 4);
+ read = stream.Read(widthBytes);
if (read != 4)
{
throw new InvalidImageContentException("The image is not a QOI image");
}
- read = stream.Read(heightBytes, 0, 4);
+ read = stream.Read(heightBytes);
if (read != 4)
{
throw new InvalidImageContentException("The image is not a QOI image");
}
- Size size = new(BitConverter.ToInt32(widthBytes), BitConverter.ToInt32(heightBytes));
+ widthBytes = widthBytes.Reverse().ToArray();
+ heightBytes = heightBytes.Reverse().ToArray();
+ Size size = new((int)BitConverter.ToUInt32(widthBytes), (int)BitConverter.ToUInt32(heightBytes));
int channels = stream.ReadByte();
if (channels == -1)
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Qoi/IdentifyQoi.cs b/tests/ImageSharp.Benchmarks/Codecs/Qoi/IdentifyQoi.cs
new file mode 100644
index 000000000..9631d80cb
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/Qoi/IdentifyQoi.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Qoi;
+using SixLabors.ImageSharp.Tests;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs.Qoi;
+
+[Config(typeof(Config.ShortMultiFramework))]
+public class IdentifyQoi
+{
+ private byte[] qoiBytes;
+
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Qoi.TestCardRGBA, TestImages.Qoi.TestCard, TestImages.Qoi.QoiLogo, TestImages.Qoi.EdgeCase, TestImages.Png.Bike)]
+ public string TestImage { get; set; }
+
+ [GlobalSetup]
+ public void ReadImages() => this.qoiBytes ??= File.ReadAllBytes(this.TestImageFullPath);
+
+ [Benchmark]
+ public ImageInfo Identify()
+ {
+ using MemoryStream memoryStream = new(this.qoiBytes);
+ return QoiDecoder.Instance.Identify(DecoderOptions.Default, memoryStream);
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
index 0ba2f4b94..217e3165c 100644
--- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
+++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
@@ -69,5 +69,8 @@
+
+
+
diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs
index 75e5a8233..55cff86d5 100644
--- a/tests/ImageSharp.Benchmarks/Program.cs
+++ b/tests/ImageSharp.Benchmarks/Program.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
namespace SixLabors.ImageSharp.Benchmarks;
@@ -15,5 +16,5 @@ public class Program
///
public static void Main(string[] args) => BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
- .Run(args);
+ .Run(args, new DebugInProcessConfig());
}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 589168f00..3db033e8a 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1029,4 +1029,16 @@ public static class TestImages
public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm";
public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm";
}
+
+ 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/Images/Input/Qoi/dice.qoi b/tests/Images/Input/Qoi/dice.qoi
new file mode 100644
index 000000000..bbc8154fc
Binary files /dev/null and b/tests/Images/Input/Qoi/dice.qoi differ
diff --git a/tests/Images/Input/Qoi/edgecase.qoi b/tests/Images/Input/Qoi/edgecase.qoi
new file mode 100644
index 000000000..ddaa5a931
Binary files /dev/null and b/tests/Images/Input/Qoi/edgecase.qoi differ
diff --git a/tests/Images/Input/Qoi/kodim10.qoi b/tests/Images/Input/Qoi/kodim10.qoi
new file mode 100644
index 000000000..eb6f20f53
Binary files /dev/null and b/tests/Images/Input/Qoi/kodim10.qoi differ
diff --git a/tests/Images/Input/Qoi/kodim23.qoi b/tests/Images/Input/Qoi/kodim23.qoi
new file mode 100644
index 000000000..078918d5a
Binary files /dev/null and b/tests/Images/Input/Qoi/kodim23.qoi differ
diff --git a/tests/Images/Input/Qoi/qoi_logo.qoi b/tests/Images/Input/Qoi/qoi_logo.qoi
new file mode 100644
index 000000000..90eef44fe
Binary files /dev/null and b/tests/Images/Input/Qoi/qoi_logo.qoi differ
diff --git a/tests/Images/Input/Qoi/testcard.qoi b/tests/Images/Input/Qoi/testcard.qoi
new file mode 100644
index 000000000..d6fccc928
Binary files /dev/null and b/tests/Images/Input/Qoi/testcard.qoi differ
diff --git a/tests/Images/Input/Qoi/testcard_rgba.qoi b/tests/Images/Input/Qoi/testcard_rgba.qoi
new file mode 100644
index 000000000..997aed4d9
Binary files /dev/null and b/tests/Images/Input/Qoi/testcard_rgba.qoi differ
diff --git a/tests/Images/Input/Qoi/wikipedia_008.qoi b/tests/Images/Input/Qoi/wikipedia_008.qoi
new file mode 100644
index 000000000..db52dc514
Binary files /dev/null and b/tests/Images/Input/Qoi/wikipedia_008.qoi differ