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.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
@ -212,6 +213,7 @@ public sealed class Configuration
/// <see cref="TgaConfigurationModule"/>. /// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>. /// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>. /// <see cref="WebpConfigurationModule"/>.
/// <see cref="QoiConfigurationModule"/>.
/// </summary> /// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns> /// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance() => new( internal static Configuration CreateDefaultInstance() => new(
@ -222,5 +224,6 @@ public sealed class Configuration
new PbmConfigurationModule(), new PbmConfigurationModule(),
new TgaConfigurationModule(), new TgaConfigurationModule(),
new TiffConfigurationModule(), 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 internal static class QoiConstants
{ {
private static readonly byte[] SMagic = Encoding.UTF8.GetBytes("qoif");
/// <summary> /// <summary>
/// Gets the bytes that indicates the image is QOI /// Gets the bytes that indicates the image is QOI
/// </summary> /// </summary>
public static ReadOnlySpan<byte> Magic => Encoding.UTF8.GetBytes("qoif"); public static ReadOnlySpan<byte> Magic => SMagic;
/// <summary> /// <summary>
/// The list of mimetypes that equate to a QOI. /// The list of mimetypes that equate to a QOI.
/// See https://github.com/phoboslab/qoi/issues/167 /// See https://github.com/phoboslab/qoi/issues/167
/// </summary> /// </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> /// <summary>
/// The list of file extensions that equate to a QOI. /// The list of file extensions that equate to a QOI.
/// </summary> /// </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. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -48,47 +50,67 @@ internal class QoiDecoderCore : IImageDecoderInternals
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
ImageMetadata metadata = new(); 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 // Read magic bytes
int read = stream.Read(magicBytes); int read = stream.Read(magicBytes);
if (read != 4 || !magicBytes.SequenceEqual(QoiConstants.Magic.ToArray())) 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 // If it's a qoi image, read the rest of properties
read = stream.Read(widthBytes); read = stream.Read(widthBytes);
if (read != 4) if (read != 4)
{ {
throw new InvalidImageContentException("The image is not a QOI image"); ThrowInvalidImageContentException();
} }
read = stream.Read(heightBytes); read = stream.Read(heightBytes);
if (read != 4) if (read != 4)
{ {
throw new InvalidImageContentException("The image is not a QOI image"); ThrowInvalidImageContentException();
} }
widthBytes = widthBytes.Reverse().ToArray(); // These numbers are in Big Endian so we have to reverse them to get the real number
heightBytes = heightBytes.Reverse().ToArray(); uint width = BinaryPrimitives.ReadUInt32BigEndian(widthBytes),
Size size = new((int)BitConverter.ToUInt32(widthBytes), (int)BitConverter.ToUInt32(heightBytes)); 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(); 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); PixelTypeInfo pixelType = new(8 * channels);
qoiMetadata.Channels = (QoiChannels)channels;
int colorSpace = stream.ReadByte(); 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); 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. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running; using BenchmarkDotNet.Running;
namespace SixLabors.ImageSharp.Benchmarks; namespace SixLabors.ImageSharp.Benchmarks;
@ -16,5 +15,5 @@ public class Program
/// </param> /// </param>
public static void Main(string[] args) => BenchmarkSwitcher public static void Main(string[] args) => BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly) .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}" /> <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Formats\Qoi\" />
</ItemGroup>
</Project> </Project>

Loading…
Cancel
Save