Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding QOI support #2446

Merged
merged 30 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9a07ee9
Adding QOI metadata and constants
LuisAlfredo92 Apr 20, 2023
6784acf
Merge branch 'main' of https://github.com/LuisAlfredo92/ImageSharp
LuisAlfredo92 Apr 20, 2023
c79dd77
Adding qoi identification
LuisAlfredo92 May 3, 2023
60d8763
Adding tests and fixing bugs
LuisAlfredo92 May 4, 2023
43bf27a
Merge branch 'SixLabors:main' into main
LuisAlfredo92 May 4, 2023
24e5ce6
Making changes from #2446 review
LuisAlfredo92 Jun 12, 2023
8b9e0a9
Merge branch 'main' of https://github.com/LuisAlfredo92/ImageSharp
LuisAlfredo92 Jun 12, 2023
a1342bf
Fixing Qoi detector
LuisAlfredo92 Jun 13, 2023
bca998d
Implementing qoi decoder
LuisAlfredo92 Jun 25, 2023
a87f781
Fixing edge case
LuisAlfredo92 Jun 25, 2023
07e6597
Finishing qoi encoder
LuisAlfredo92 Jun 28, 2023
4a494c7
Merge branch 'main' into main
LuisAlfredo92 Jun 28, 2023
c92a4ec
Adding extensions was easier than I though
LuisAlfredo92 Jun 28, 2023
8729fed
Fixing configuration tests
LuisAlfredo92 Jun 28, 2023
aed9acd
Fixing StyleCop
LuisAlfredo92 Jun 29, 2023
6245666
Fixing most of things of review
LuisAlfredo92 Jun 29, 2023
cf3271e
Update .gitattributes and deleting images to repush
LuisAlfredo92 Jun 29, 2023
d671006
Reuploading images
LuisAlfredo92 Jun 29, 2023
02212bd
Optimizing Decoder
LuisAlfredo92 Jun 30, 2023
88de0a6
Fixing encoder optimizations
LuisAlfredo92 Jun 30, 2023
383aa22
Fixing and optimizing decoder and encoder
LuisAlfredo92 Jun 30, 2023
bf9f1f3
Fixing and making easier to read encoder
LuisAlfredo92 Jun 30, 2023
f7b4f49
Fixing StyleCop
LuisAlfredo92 Jun 30, 2023
3f1fe69
Fixing declarations
LuisAlfredo92 Jul 3, 2023
ef78f98
Adding Channels and ColorSpace validations to De-/Encoder
LuisAlfredo92 Jul 3, 2023
dea1ee8
Optimizing avoiding casts to byte
LuisAlfredo92 Jul 3, 2023
472bc04
Fixing extensions, class access and StyleCop
LuisAlfredo92 Jul 5, 2023
1069a6d
Merge branch 'main' into main
antonfirsov Jul 5, 2023
eb5c6a7
private QoiMetadata(QoiMetadata other)
antonfirsov Jul 6, 2023
59c4a6a
Merge branch 'main' into main
JimBobSquarePants Jul 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
*.bmp filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.qoi filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
Expand Down
13 changes: 13 additions & 0 deletions ImageSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,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
Expand Down Expand Up @@ -698,6 +710,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}
Expand Down
2 changes: 1 addition & 1 deletion shared-infrastructure
24 changes: 24 additions & 0 deletions src/ImageSharp/Common/Helpers/Numerics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,30 @@ public static int LeastCommonMultiple(int a, int b)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo8(nint x) => x & 7;

/// <summary>
/// Calculates <paramref name="x"/> % 64
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo64(int x) => x & 63;

/// <summary>
/// Calculates <paramref name="x"/> % 64
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo64(nint x) => x & 63;

/// <summary>
/// Calculates <paramref name="x"/> % 256
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo256(int x) => x & 255;

/// <summary>
/// Calculates <paramref name="x"/> % 256
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo256(nint x) => x & 255;

/// <summary>
/// Fast (x mod m) calculator, with the restriction that
/// <paramref name="m"/> should be power of 2.
Expand Down
5 changes: 4 additions & 1 deletion src/ImageSharp/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -212,6 +213,7 @@ public void Configure(IImageFormatConfigurationModule 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(
Expand All @@ -222,5 +224,6 @@ public void Configure(IImageFormatConfigurationModule configuration)
new PbmConfigurationModule(),
new TgaConfigurationModule(),
new TiffConfigurationModule(),
new WebpConfigurationModule());
new WebpConfigurationModule(),
new QoiConfigurationModule());
}
102 changes: 102 additions & 0 deletions src/ImageSharp/Formats/ImageExtensions.Save.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Qoi;
LuisAlfredo92 marked this conversation as resolved.
Show resolved Hide resolved
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Tiff;
Expand Down Expand Up @@ -836,4 +837,105 @@ public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance),
cancellationToken);

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsQoi(this Image source, string path) => SaveAsQoi(source, path, default);

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsQoiAsync(this Image source, string path) => SaveAsQoiAsync(source, path, default);

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsQoiAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsQoiAsync(source, path, default, cancellationToken);

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsQoi(this Image source, string path, QoiEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance));

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsQoiAsync(this Image source, string path, QoiEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance),
cancellationToken);

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsQoi(this Image source, Stream stream)
=> SaveAsQoi(source, stream, default);

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsQoiAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsQoiAsync(source, stream, default, cancellationToken);

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsQoi(this Image source, Stream stream, QoiEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance));

/// <summary>
/// Saves the image to the given stream with the Qoi format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsQoiAsync(this Image source, Stream stream, QoiEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance),
cancellationToken);
}
20 changes: 20 additions & 0 deletions src/ImageSharp/Formats/Qoi/MetadataExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Metadata;

namespace SixLabors.ImageSharp;

/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the qoi format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="QoiMetadata"/>.</returns>
public static QoiMetadata GetQoiMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(QoiFormat.Instance);
}
20 changes: 20 additions & 0 deletions src/ImageSharp/Formats/Qoi/QoiChannels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Formats.Qoi;

/// <summary>
/// Provides enumeration of available QOI color channels.
/// </summary>
public enum QoiChannels
{
/// <summary>
/// Each pixel is an R,G,B triple.
/// </summary>
Rgb = 3,

/// <summary>
/// Each pixel is an R,G,B triple, followed by an alpha sample.
/// </summary>
Rgba = 4
}
56 changes: 56 additions & 0 deletions src/ImageSharp/Formats/Qoi/QoiChunk.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Formats.Qoi;

/// <summary>
/// Enum that contains the operations that encoder and decoder must process, written
/// in binary to be easier to compare them in the reference
/// </summary>
internal enum QoiChunk
{
/// <summary>
/// Indicates that the operation is QOI_OP_RGB where the RGB values are written
/// in one byte each one after this marker
/// </summary>
QoiOpRgb = 0b11111110,

/// <summary>
/// Indicates that the operation is QOI_OP_RGBA where the RGBA values are written
/// in one byte each one after this marker
/// </summary>
QoiOpRgba = 0b11111111,

/// <summary>
/// Indicates that the operation is QOI_OP_INDEX where one byte contains a 2-bit
/// marker (0b00) followed by an index on the previously seen pixels array 0..63
/// </summary>
QoiOpIndex = 0b00000000,

/// <summary>
/// Indicates that the operation is QOI_OP_DIFF where one byte contains a 2-bit
/// marker (0b01) followed by 2-bit differences in red, green and blue channel
/// with the previous pixel with a bias of 2 (-2..1)
/// </summary>
QoiOpDiff = 0b01000000,

/// <summary>
/// Indicates that the operation is QOI_OP_LUMA where one byte contains a 2-bit
/// marker (0b01) followed by a 6-bits number that indicates the difference of
/// the green channel with the previous pixel. Then another byte that contains
/// a 4-bit number that indicates the difference of the red channel minus the
/// previous difference, and another 4-bit number that indicates the difference
/// of the blue channel minus the green difference
/// Example: 0b10[6-bits diff green] 0b[6-bits dr-dg][6-bits db-dg]
/// dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
/// db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
/// </summary>
QoiOpLuma = 0b10000000,

/// <summary>
/// Indicates that the operation is QOI_OP_RUN where one byte contains a 2-bit
/// marker (0b11) followed by a 6-bits number that indicates the times that the
/// previous pixel is repeated
/// </summary>
QoiOpRun = 0b11000000
}
22 changes: 22 additions & 0 deletions src/ImageSharp/Formats/Qoi/QoiColorSpace.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
namespace SixLabors.ImageSharp.Formats.Qoi;

/// <summary>
/// Enum for the different QOI color spaces.
/// </summary>
public enum QoiColorSpace
{
/// <summary>
/// sRGB color space with linear alpha value
/// </summary>
SrgbWithLinearAlpha,

/// <summary>
/// All the values in the color space are linear
/// </summary>
AllChannelsLinear
}
18 changes: 18 additions & 0 deletions src/ImageSharp/Formats/Qoi/QoiConfigurationModule.cs
Original file line number Diff line number Diff line change
@@ -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());
}
}
27 changes: 27 additions & 0 deletions src/ImageSharp/Formats/Qoi/QoiConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Text;

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 => SMagic;

/// <summary>
/// Gets the list of mimetypes that equate to a QOI.
/// See https://github.com/phoboslab/qoi/issues/167
/// </summary>
public static string[] MimeTypes { get; } = { "image/qoi", "image/x-qoi", "image/vnd.qoi" };

/// <summary>
/// Gets the list of file extensions that equate to a QOI.
/// </summary>
public static string[] FileExtensions { get; } = { "qoi" };
}
Loading