Browse Source

Making changes from #2446 review

-Adding validation for size, channels and colorspace
-Refactoring to throw general exceptions
-Creating tests
-Using other better data types
qoi
LuisAlfredo92 3 years ago
parent
commit
24e5ce6964
No known key found for this signature in database GPG Key ID: 13A8436905993B8F
  1. 5
      src/ImageSharp/Configuration.cs
  2. 18
      src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs
  3. 8
      src/ImageSharp/Formats/Qoi/QoiConstants.cs
  4. 44
      src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
  5. 27
      src/ImageSharp/Formats/Qoi/QoiImageFormatDetector.cs
  6. 3
      tests/ImageSharp.Benchmarks/Program.cs
  7. 28
      tests/ImageSharp.Tests/Formats/Qoi/QoiDecoderTests.cs
  8. 4
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj

5
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
/// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>.
/// <see cref="QoiConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
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());
}

18
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;
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the qoi format.
/// </summary>
public sealed class QoiConfigurationModule : IImageFormatConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
configuration.ImageFormatsManager.SetDecoder(QoiFormat.Instance, QoiDecoder.Instance);
//configuration.ImageFormatsManager.SetEncoder(QoiFormat.Instance, new QoiEncoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new QoiImageFormatDetector());
}
}

8
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");
/// <summary>
/// Gets the bytes that indicates the image is QOI
/// </summary>
public static ReadOnlySpan<byte> Magic => Encoding.UTF8.GetBytes("qoif");
public static ReadOnlySpan<byte> Magic => SMagic;
/// <summary>
/// The list of mimetypes that equate to a QOI.
/// See https://github.com/phoboslab/qoi/issues/167
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
public static readonly string[] MimeTypes = { "image/qoi", "image/x-qoi", "image/vnd.qoi" };
/// <summary>
/// The list of file extensions that equate to a QOI.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "qoi" };
public static readonly string[] FileExtensions = { "qoi" };
}

44
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<byte> magicBytes = stackalloc byte[4];
Span<byte> widthBytes = stackalloc byte[4];
Span<byte> 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.");
}

27
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;
/// <summary>
/// Detects qoi file headers
/// </summary>
public class QoiImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 14;
/// <inheritdoc/>
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{
format = this.IsSupportedFileFormat(header) ? QoiFormat.Instance : null;
return format != null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
=> header.Length == this.HeaderSize && header[..4] == QoiConstants.Magic;
}

3
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
/// </param>
public static void Main(string[] args) => BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
.Run(args, new DebugInProcessConfig());
.Run(args);
}

28
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);
}
}

4
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -73,5 +73,9 @@
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Folder Include="Formats\Qoi\" />
</ItemGroup>
</Project>

Loading…
Cancel
Save