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