Skip to content

Commit 24e5ce6

Browse files
committed
Making changes from #2446 review
-Adding validation for size, channels and colorspace -Refactoring to throw general exceptions -Creating tests -Using other better data types
1 parent 60d8763 commit 24e5ce6

File tree

8 files changed

+120
-17
lines changed

8 files changed

+120
-17
lines changed

src/ImageSharp/Configuration.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using SixLabors.ImageSharp.Formats.Jpeg;
99
using SixLabors.ImageSharp.Formats.Pbm;
1010
using SixLabors.ImageSharp.Formats.Png;
11+
using SixLabors.ImageSharp.Formats.Qoi;
1112
using SixLabors.ImageSharp.Formats.Tga;
1213
using SixLabors.ImageSharp.Formats.Tiff;
1314
using SixLabors.ImageSharp.Formats.Webp;
@@ -212,6 +213,7 @@ public void Configure(IImageFormatConfigurationModule configuration)
212213
/// <see cref="TgaConfigurationModule"/>.
213214
/// <see cref="TiffConfigurationModule"/>.
214215
/// <see cref="WebpConfigurationModule"/>.
216+
/// <see cref="QoiConfigurationModule"/>.
215217
/// </summary>
216218
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
217219
internal static Configuration CreateDefaultInstance() => new(
@@ -222,5 +224,6 @@ public void Configure(IImageFormatConfigurationModule configuration)
222224
new PbmConfigurationModule(),
223225
new TgaConfigurationModule(),
224226
new TiffConfigurationModule(),
225-
new WebpConfigurationModule());
227+
new WebpConfigurationModule(),
228+
new QoiConfigurationModule());
226229
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Formats.Qoi;
5+
6+
/// <summary>
7+
/// Registers the image encoders, decoders and mime type detectors for the qoi format.
8+
/// </summary>
9+
public sealed class QoiConfigurationModule : IImageFormatConfigurationModule
10+
{
11+
/// <inheritdoc/>
12+
public void Configure(Configuration configuration)
13+
{
14+
configuration.ImageFormatsManager.SetDecoder(QoiFormat.Instance, QoiDecoder.Instance);
15+
//configuration.ImageFormatsManager.SetEncoder(QoiFormat.Instance, new QoiEncoder());
16+
configuration.ImageFormatsManager.AddImageFormatDetector(new QoiImageFormatDetector());
17+
}
18+
}

src/ImageSharp/Formats/Qoi/QoiConstants.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ namespace SixLabors.ImageSharp.Formats.Qoi;
77

88
internal static class QoiConstants
99
{
10+
private static readonly byte[] SMagic = Encoding.UTF8.GetBytes("qoif");
11+
1012
/// <summary>
1113
/// Gets the bytes that indicates the image is QOI
1214
/// </summary>
13-
public static ReadOnlySpan<byte> Magic => Encoding.UTF8.GetBytes("qoif");
15+
public static ReadOnlySpan<byte> Magic => SMagic;
1416

1517
/// <summary>
1618
/// The list of mimetypes that equate to a QOI.
1719
/// See https://github.com/phoboslab/qoi/issues/167
1820
/// </summary>
19-
public static readonly IEnumerable<string> MimeTypes = new[] { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
21+
public static readonly string[] MimeTypes = { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
2022

2123
/// <summary>
2224
/// The list of file extensions that equate to a QOI.
2325
/// </summary>
24-
public static readonly IEnumerable<string> FileExtensions = new[] { "qoi" };
26+
public static readonly string[] FileExtensions = { "qoi" };
2527
}

src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Buffers.Binary;
5+
using System.Diagnostics.CodeAnalysis;
46
using SixLabors.ImageSharp.IO;
57
using SixLabors.ImageSharp.Memory;
68
using SixLabors.ImageSharp.Metadata;
@@ -48,47 +50,67 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
4850
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
4951
{
5052
ImageMetadata metadata = new();
53+
QoiMetadata qoiMetadata = metadata.GetQoiMetadata();
5154

52-
byte[] magicBytes = new byte[4], widthBytes = new byte[4], heightBytes = new byte[4];
55+
Span<byte> magicBytes = stackalloc byte[4];
56+
Span<byte> widthBytes = stackalloc byte[4];
57+
Span<byte> heightBytes = stackalloc byte[4];
5358

5459
// Read magic bytes
5560
int read = stream.Read(magicBytes);
5661
if (read != 4 || !magicBytes.SequenceEqual(QoiConstants.Magic.ToArray()))
5762
{
58-
throw new InvalidImageContentException("The image is not a QOI image");
63+
ThrowInvalidImageContentException();
5964
}
6065

6166
// If it's a qoi image, read the rest of properties
6267
read = stream.Read(widthBytes);
6368
if (read != 4)
6469
{
65-
throw new InvalidImageContentException("The image is not a QOI image");
70+
ThrowInvalidImageContentException();
6671
}
6772

6873
read = stream.Read(heightBytes);
6974
if (read != 4)
7075
{
71-
throw new InvalidImageContentException("The image is not a QOI image");
76+
ThrowInvalidImageContentException();
7277
}
7378

74-
widthBytes = widthBytes.Reverse().ToArray();
75-
heightBytes = heightBytes.Reverse().ToArray();
76-
Size size = new((int)BitConverter.ToUInt32(widthBytes), (int)BitConverter.ToUInt32(heightBytes));
79+
// These numbers are in Big Endian so we have to reverse them to get the real number
80+
uint width = BinaryPrimitives.ReadUInt32BigEndian(widthBytes),
81+
height = BinaryPrimitives.ReadUInt32BigEndian(heightBytes);
82+
if (width == 0 || height == 0)
83+
{
84+
throw new InvalidImageContentException(
85+
$"The image has an invalid size: width = {width}, height = {height}");
86+
}
87+
88+
qoiMetadata.Width = width;
89+
qoiMetadata.Height = height;
90+
91+
Size size = new((int)width, (int)height);
7792

7893
int channels = stream.ReadByte();
79-
if (channels == -1)
94+
if (channels is -1 or (not 3 and not 4))
8095
{
81-
throw new InvalidImageContentException("The image is not a QOI image");
96+
ThrowInvalidImageContentException();
8297
}
8398

8499
PixelTypeInfo pixelType = new(8 * channels);
100+
qoiMetadata.Channels = (QoiChannels)channels;
85101

86102
int colorSpace = stream.ReadByte();
87-
if (colorSpace == -1)
103+
if (colorSpace is -1 or (not 0 and not 1))
88104
{
89-
throw new InvalidImageContentException("The image is not a QOI image");
105+
ThrowInvalidImageContentException();
90106
}
91107

108+
qoiMetadata.ColorSpace = (QoiColorSpace)colorSpace;
109+
92110
return new ImageInfo(pixelType, size, metadata);
93111
}
112+
113+
[DoesNotReturn]
114+
private static void ThrowInvalidImageContentException()
115+
=> throw new InvalidImageContentException("The image is not a valid QOI image.");
94116
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers.Binary;
5+
using System.Diagnostics.CodeAnalysis;
6+
using SixLabors.ImageSharp.Formats.Png;
7+
8+
namespace SixLabors.ImageSharp.Formats.Qoi;
9+
10+
/// <summary>
11+
/// Detects qoi file headers
12+
/// </summary>
13+
public class QoiImageFormatDetector : IImageFormatDetector
14+
{
15+
/// <inheritdoc/>
16+
public int HeaderSize => 14;
17+
18+
/// <inheritdoc/>
19+
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
20+
{
21+
format = this.IsSupportedFileFormat(header) ? QoiFormat.Instance : null;
22+
return format != null;
23+
}
24+
25+
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
26+
=> header.Length == this.HeaderSize && header[..4] == QoiConstants.Magic;
27+
}

tests/ImageSharp.Benchmarks/Program.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
using BenchmarkDotNet.Configs;
54
using BenchmarkDotNet.Running;
65

76
namespace SixLabors.ImageSharp.Benchmarks;
@@ -16,5 +15,5 @@ public class Program
1615
/// </param>
1716
public static void Main(string[] args) => BenchmarkSwitcher
1817
.FromAssembly(typeof(Program).Assembly)
19-
.Run(args, new DebugInProcessConfig());
18+
.Run(args);
2019
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Tests.Formats.Qoi;
5+
6+
[Trait("Format", "Qoi")]
7+
[ValidateDisposedMemoryAllocations]
8+
public class QoiDecoderTests
9+
{
10+
[Theory]
11+
[InlineData(TestImages.Qoi.Dice)]
12+
[InlineData(TestImages.Qoi.EdgeCase)]
13+
[InlineData(TestImages.Qoi.Kodim10)]
14+
[InlineData(TestImages.Qoi.Kodim23)]
15+
[InlineData(TestImages.Qoi.QoiLogo)]
16+
[InlineData(TestImages.Qoi.TestCard)]
17+
[InlineData(TestImages.Qoi.TestCardRGBA)]
18+
[InlineData(TestImages.Qoi.Wikipedia008)]
19+
public void Identify(string imagePath)
20+
{
21+
TestFile testFile = TestFile.Create(imagePath);
22+
using MemoryStream stream = new(testFile.Bytes, false);
23+
24+
ImageInfo imageInfo = Image.Identify(stream);
25+
26+
Assert.NotNull(imageInfo);
27+
}
28+
}

tests/ImageSharp.Tests/ImageSharp.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,9 @@
7373
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
7474
</ItemGroup>
7575

76+
<ItemGroup>
77+
<Folder Include="Formats\Qoi\" />
78+
</ItemGroup>
79+
7680
</Project>
7781

0 commit comments

Comments
 (0)