diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index d4d055823..39fcef9c4 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/Qoi/QoiConfigurationModule.cs b/src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs
new file mode 100644
index 000000000..ab0d0aa2c
--- /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
index acebb13a7..62508d3be 100644
--- a/src/ImageSharp/Formats/Qoi/QoiConstants.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiConstants.cs
@@ -7,19 +7,21 @@ 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 => Encoding.UTF8.GetBytes("qoif");
+ public static ReadOnlySpan Magic => SMagic;
///
/// The list of mimetypes that equate to a QOI.
/// See https://github.com/phoboslab/qoi/issues/167
///
- public static readonly IEnumerable MimeTypes = new[] { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
+ public static readonly string[] MimeTypes = { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
///
/// The list of file extensions that equate to a QOI.
///
- public static readonly IEnumerable FileExtensions = new[] { "qoi" };
+ public static readonly string[] FileExtensions = { "qoi" };
}
diff --git a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
index 1594165ba..5cec6c244 100644
--- a/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Buffers.Binary;
+using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@@ -48,47 +50,67 @@ internal class QoiDecoderCore : IImageDecoderInternals
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
ImageMetadata metadata = new();
+ QoiMetadata qoiMetadata = metadata.GetQoiMetadata();
- byte[] magicBytes = new byte[4], widthBytes = new byte[4], heightBytes = new byte[4];
+ 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()))
{
- throw new InvalidImageContentException("The image is not a QOI image");
+ ThrowInvalidImageContentException();
}
// If it's a qoi image, read the rest of properties
read = stream.Read(widthBytes);
if (read != 4)
{
- throw new InvalidImageContentException("The image is not a QOI image");
+ ThrowInvalidImageContentException();
}
read = stream.Read(heightBytes);
if (read != 4)
{
- throw new InvalidImageContentException("The image is not a QOI image");
+ ThrowInvalidImageContentException();
}
- widthBytes = widthBytes.Reverse().ToArray();
- heightBytes = heightBytes.Reverse().ToArray();
- Size size = new((int)BitConverter.ToUInt32(widthBytes), (int)BitConverter.ToUInt32(heightBytes));
+ // These numbers are in Big Endian so we have to reverse them to get the real number
+ uint width = BinaryPrimitives.ReadUInt32BigEndian(widthBytes),
+ height = BinaryPrimitives.ReadUInt32BigEndian(heightBytes);
+ if (width == 0 || height == 0)
+ {
+ throw new InvalidImageContentException(
+ $"The image has an invalid size: width = {width}, height = {height}");
+ }
+
+ qoiMetadata.Width = width;
+ qoiMetadata.Height = height;
+
+ Size size = new((int)width, (int)height);
int channels = stream.ReadByte();
- if (channels == -1)
+ if (channels is -1 or (not 3 and not 4))
{
- throw new InvalidImageContentException("The image is not a QOI image");
+ ThrowInvalidImageContentException();
}
PixelTypeInfo pixelType = new(8 * channels);
+ qoiMetadata.Channels = (QoiChannels)channels;
int colorSpace = stream.ReadByte();
- if (colorSpace == -1)
+ if (colorSpace is -1 or (not 0 and not 1))
{
- throw new InvalidImageContentException("The image is not a QOI image");
+ ThrowInvalidImageContentException();
}
+ qoiMetadata.ColorSpace = (QoiColorSpace)colorSpace;
+
return new ImageInfo(pixelType, size, metadata);
}
+
+ [DoesNotReturn]
+ private static void ThrowInvalidImageContentException()
+ => throw new InvalidImageContentException("The image is not a valid QOI image.");
}
diff --git a/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs b/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs
new file mode 100644
index 000000000..720e308b3
--- /dev/null
+++ b/src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers.Binary;
+using System.Diagnostics.CodeAnalysis;
+using SixLabors.ImageSharp.Formats.Png;
+
+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 && header[..4] == QoiConstants.Magic;
+}
diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs
index 55cff86d5..75e5a8233 100644
--- a/tests/ImageSharp.Benchmarks/Program.cs
+++ b/tests/ImageSharp.Benchmarks/Program.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
namespace SixLabors.ImageSharp.Benchmarks;
@@ -16,5 +15,5 @@ public class Program
///
public static void Main(string[] args) => BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
- .Run(args, new DebugInProcessConfig());
+ .Run(args);
}
diff --git a/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs
new file mode 100644
index 000000000..9a5e0bfaf
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Tests.Formats.Qoi;
+
+[Trait("Format", "Qoi")]
+[ValidateDisposedMemoryAllocations]
+public class QoiDecoderTests
+{
+ [Theory]
+ [InlineData(TestImages.Qoi.Dice)]
+ [InlineData(TestImages.Qoi.EdgeCase)]
+ [InlineData(TestImages.Qoi.Kodim10)]
+ [InlineData(TestImages.Qoi.Kodim23)]
+ [InlineData(TestImages.Qoi.QoiLogo)]
+ [InlineData(TestImages.Qoi.TestCard)]
+ [InlineData(TestImages.Qoi.TestCardRGBA)]
+ [InlineData(TestImages.Qoi.Wikipedia008)]
+ public void Identify(string imagePath)
+ {
+ TestFile testFile = TestFile.Create(imagePath);
+ using MemoryStream stream = new(testFile.Bytes, false);
+
+ ImageInfo imageInfo = Image.Identify(stream);
+
+ Assert.NotNull(imageInfo);
+ }
+}
diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
index a6197b600..48daa5ead 100644
--- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
+++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
@@ -73,5 +73,9 @@
+
+
+
+