diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs
index 1bb78a84db..4bc66406bb 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs
@@ -9,8 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
///
/// Represents a single frame component
///
- internal struct FrameComponent : IDisposable
+ internal class FrameComponent : IDisposable
{
+ #pragma warning disable SA1401 // Fields should be private
///
/// Gets or sets the component Id
///
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs
index dc8d477f01..3df47f5245 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs
@@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
internal sealed class JpegDecoderCore : IDisposable
{
+#pragma warning disable SA1401 // Fields should be private
///
/// The global configuration
///
@@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private HuffmanTables acHuffmanTables;
- private Frame frame;
+ internal Frame Frame;
private ComponentBlocks components;
@@ -42,11 +43,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private ushort resetInterval;
- private int imageWidth;
+ internal int ImageWidth { get; private set; }
- private int imageHeight;
+ internal int ImageHeight { get; private set; }
- private int numberOfComponents;
+ internal int NumberOfComponents { get; private set; }
///
/// Whether the image has a EXIF header
@@ -137,12 +138,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public Image Decode(Stream stream)
where TPixel : struct, IPixel
{
- this.InputStream = stream;
-
- var metadata = new ImageMetaData();
- this.ParseStream(metadata, false);
+ ImageMetaData metadata = this.ParseStream(stream);
- var image = new Image(this.configuration, this.imageWidth, this.imageHeight, metadata);
+ var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, metadata);
this.FillPixelData(image);
this.AssignResolution(image);
return image;
@@ -151,7 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
public void Dispose()
{
- this.frame?.Dispose();
+ this.Frame?.Dispose();
this.components?.Dispose();
this.quantizationTables?.Dispose();
this.dcHuffmanTables?.Dispose();
@@ -159,13 +157,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.pixelArea.Dispose();
// Set large fields to null.
- this.frame = null;
+ this.Frame = null;
this.components = null;
this.quantizationTables = null;
this.dcHuffmanTables = null;
this.acHuffmanTables = null;
}
+ internal ImageMetaData ParseStream(Stream stream)
+ {
+ this.InputStream = stream;
+
+ var metadata = new ImageMetaData();
+ this.ParseStream(metadata, false);
+ return metadata;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetBlockBufferOffset(ref Component component, int row, int col)
{
@@ -262,18 +269,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream);
}
- this.imageWidth = this.frame.SamplesPerLine;
- this.imageHeight = this.frame.Scanlines;
- this.components = new ComponentBlocks { Components = new Component[this.frame.ComponentCount] };
+ this.ImageWidth = this.Frame.SamplesPerLine;
+ this.ImageHeight = this.Frame.Scanlines;
+ this.components = new ComponentBlocks { Components = new Component[this.Frame.ComponentCount] };
for (int i = 0; i < this.components.Components.Length; i++)
{
- ref var frameComponent = ref this.frame.Components[i];
+ ref var frameComponent = ref this.Frame.Components[i];
var component = new Component
{
Scale = new System.Numerics.Vector2(
- frameComponent.HorizontalFactor / (float)this.frame.MaxHorizontalFactor,
- frameComponent.VerticalFactor / (float)this.frame.MaxVerticalFactor),
+ frameComponent.HorizontalFactor / (float)this.Frame.MaxHorizontalFactor,
+ frameComponent.VerticalFactor / (float)this.Frame.MaxVerticalFactor),
BlocksPerLine = frameComponent.BlocksPerLine,
BlocksPerColumn = frameComponent.BlocksPerColumn
};
@@ -282,7 +289,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.components.Components[i] = component;
}
- this.numberOfComponents = this.components.Components.Length;
+ this.NumberOfComponents = this.components.Components.Length;
}
///
@@ -293,21 +300,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private void FillPixelData(Image image)
where TPixel : struct, IPixel
{
- if (this.numberOfComponents > 4)
+ if (this.NumberOfComponents > 4)
{
- throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.numberOfComponents}");
+ throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.NumberOfComponents}");
}
- this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.numberOfComponents);
+ this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.NumberOfComponents);
this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height);
- if (this.numberOfComponents == 1)
+ if (this.NumberOfComponents == 1)
{
this.FillGrayScaleImage(image);
return;
}
- if (this.numberOfComponents == 3)
+ if (this.NumberOfComponents == 3)
{
if (this.adobe.Equals(default(Adobe)) || this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYCbCr)
{
@@ -319,7 +326,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
}
}
- if (this.numberOfComponents == 4)
+ if (this.NumberOfComponents == 4)
{
if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYcck)
{
@@ -601,14 +608,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// The current frame marker.
private void ProcessStartOfFrameMarker(int remaining, FileMarker frameMarker)
{
- if (this.frame != null)
+ if (this.Frame != null)
{
throw new ImageFormatException("Multiple SOF markers. Only single frame jpegs supported.");
}
this.InputStream.Read(this.temp, 0, remaining);
- this.frame = new Frame
+ this.Frame = new Frame
{
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
@@ -623,10 +630,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
int index = 6;
// No need to pool this. They max out at 4
- this.frame.ComponentIds = new byte[this.frame.ComponentCount];
- this.frame.Components = new FrameComponent[this.frame.ComponentCount];
+ this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
+ this.Frame.Components = new FrameComponent[this.Frame.ComponentCount];
- for (int i = 0; i < this.frame.Components.Length; i++)
+ for (int i = 0; i < this.Frame.Components.Length; i++)
{
int h = this.temp[index + 1] >> 4;
int v = this.temp[index + 1] & 15;
@@ -641,19 +648,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
maxV = v;
}
- ref var component = ref this.frame.Components[i];
+ var component = new FrameComponent();
+ this.Frame.Components[i] = component;
component.Id = this.temp[index];
component.HorizontalFactor = h;
component.VerticalFactor = v;
component.QuantizationIdentifier = this.temp[index + 2];
- this.frame.ComponentIds[i] = component.Id;
+ this.Frame.ComponentIds[i] = component.Id;
index += 3;
}
- this.frame.MaxHorizontalFactor = maxH;
- this.frame.MaxVerticalFactor = maxV;
+ this.Frame.MaxHorizontalFactor = maxH;
+ this.Frame.MaxVerticalFactor = maxV;
this.PrepareComponents();
}
@@ -729,9 +737,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
componentIndex = -1;
int selector = this.InputStream.ReadByte();
- for (int j = 0; j < this.frame.ComponentIds.Length; j++)
+ for (int j = 0; j < this.Frame.ComponentIds.Length; j++)
{
- byte id = this.frame.ComponentIds[j];
+ byte id = this.Frame.ComponentIds[j];
if (selector == id)
{
componentIndex = j;
@@ -743,7 +751,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException("Unknown component selector");
}
- ref FrameComponent component = ref this.frame.Components[componentIndex];
+ ref FrameComponent component = ref this.Frame.Components[componentIndex];
int tableSpec = this.InputStream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15;
@@ -757,11 +765,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
var scanDecoder = default(ScanDecoder);
scanDecoder.DecodeScan(
- this.frame,
+ this.Frame,
this.InputStream,
this.dcHuffmanTables,
this.acHuffmanTables,
- this.frame.Components,
+ this.Frame.Components,
componentIndex,
selectorsCount,
this.resetInterval,
@@ -827,14 +835,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
private void PrepareComponents()
{
- int mcusPerLine = (int)MathF.Ceiling(this.frame.SamplesPerLine / 8F / this.frame.MaxHorizontalFactor);
- int mcusPerColumn = (int)MathF.Ceiling(this.frame.Scanlines / 8F / this.frame.MaxVerticalFactor);
+ int mcusPerLine = (int)MathF.Ceiling(this.Frame.SamplesPerLine / 8F / this.Frame.MaxHorizontalFactor);
+ int mcusPerColumn = (int)MathF.Ceiling(this.Frame.Scanlines / 8F / this.Frame.MaxVerticalFactor);
- for (int i = 0; i < this.frame.ComponentCount; i++)
+ for (int i = 0; i < this.Frame.ComponentCount; i++)
{
- ref var component = ref this.frame.Components[i];
- int blocksPerLine = (int)MathF.Ceiling(MathF.Ceiling(this.frame.SamplesPerLine / 8F) * component.HorizontalFactor / this.frame.MaxHorizontalFactor);
- int blocksPerColumn = (int)MathF.Ceiling(MathF.Ceiling(this.frame.Scanlines / 8F) * component.VerticalFactor / this.frame.MaxVerticalFactor);
+ ref var component = ref this.Frame.Components[i];
+ int blocksPerLine = (int)MathF.Ceiling(MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * component.HorizontalFactor / this.Frame.MaxHorizontalFactor);
+ int blocksPerColumn = (int)MathF.Ceiling(MathF.Ceiling(this.Frame.Scanlines / 8F) * component.VerticalFactor / this.Frame.MaxVerticalFactor);
int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor;
int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor;
@@ -846,8 +854,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
component.BlocksPerColumn = blocksPerColumn;
}
- this.frame.McusPerLine = mcusPerLine;
- this.frame.McusPerColumn = mcusPerColumn;
+ this.Frame.McusPerLine = mcusPerLine;
+ this.Frame.McusPerColumn = mcusPerColumn;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs
new file mode 100644
index 0000000000..a6efa3f2fb
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs
@@ -0,0 +1,238 @@
+namespace SixLabors.ImageSharp.Tests
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using System.Numerics;
+ using System.Reflection;
+
+ using BitMiracle.LibJpeg.Classic;
+
+ using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
+ using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
+ using SixLabors.ImageSharp.PixelFormats;
+
+ using Xunit;
+
+ internal static class LibJpegTools
+ {
+ public unsafe struct Block
+ {
+ public Block(short[] data)
+ {
+ this.Data = data;
+ }
+
+ public short[] Data { get; }
+
+ //public fixed short Data[64];
+
+ //public Block8x8(short[] data)
+ //{
+ // fixed (short* p = Data)
+ // {
+ // for (int i = 0; i < 64; i++)
+ // {
+ // p[i] = data[i];
+ // }
+ // }
+ //}
+
+ public short this[int x, int y]
+ {
+ get => this.Data[y * 8 + x];
+ set => this.Data[y * 8 + x] = value;
+ }
+ }
+
+ public class SpectralData
+ {
+ public int ComponentCount { get; private set; }
+
+ public ComponentData[] Components { get; private set; }
+
+ private SpectralData(Array wholeImage)
+ {
+ this.ComponentCount = 0;
+
+ for (int i = 0; i < wholeImage.Length && wholeImage.GetValue(i) != null; i++)
+ {
+ this.ComponentCount++;
+ }
+
+ this.Components = new ComponentData[this.ComponentCount];
+
+ for (int i = 0; i < this.ComponentCount; i++)
+ {
+ object jVirtArray = wholeImage.GetValue(i);
+ Array bloxSource = (Array)GetNonPublicMember(jVirtArray, "m_buffer");
+
+ this.Components[i] = ComponentData.Load(bloxSource, i);
+ }
+ }
+
+ private SpectralData(ComponentData[] components)
+ {
+ this.ComponentCount = components.Length;
+ this.Components = components;
+ }
+
+ public static SpectralData Load(jpeg_decompress_struct cinfo)
+ {
+ //short[][][] result = new short[cinfo.Image_height][][];
+ //int blockPerMcu = (int)GetNonPublicMember(cinfo, "m_blocks_in_MCU");
+ //int mcuPerRow = (int)GetNonPublicMember(cinfo, "m_MCUs_per_row");
+ //int mcuRows = (int)GetNonPublicMember(cinfo, "m_MCU_rows_in_scan");
+
+ object coefController = GetNonPublicMember(cinfo, "m_coef");
+ Array wholeImage = (Array)GetNonPublicMember(coefController, "m_whole_image");
+
+ var result = new SpectralData(wholeImage);
+
+ return result;
+ }
+
+ public static SpectralData Load(Stream fileStream)
+ {
+ jpeg_error_mgr err = new jpeg_error_mgr();
+ jpeg_decompress_struct cinfo = new jpeg_decompress_struct(err);
+
+ cinfo.jpeg_stdio_src(fileStream);
+ cinfo.jpeg_read_header(true);
+ cinfo.Buffered_image = true;
+ cinfo.Do_block_smoothing = false;
+
+ cinfo.jpeg_start_decompress();
+
+ var output = CreateOutputArray(cinfo);
+ for (int scan = 0; scan < cinfo.Input_scan_number; scan++)
+ {
+ cinfo.jpeg_start_output(scan);
+ for (int i = 0; i < cinfo.Image_height; i++)
+ {
+ int numScanlines = cinfo.jpeg_read_scanlines(output, 1);
+ if (numScanlines != 1) throw new Exception("?");
+ }
+ }
+
+ var result = SpectralData.Load(cinfo);
+ return result;
+ }
+
+ private static byte[][] CreateOutputArray(jpeg_decompress_struct cinfo)
+ {
+ byte[][] output = new byte[cinfo.Image_height][];
+ for (int i = 0; i < cinfo.Image_height; i++)
+ {
+ output[i] = new byte[cinfo.Image_width * cinfo.Num_components];
+ }
+ return output;
+ }
+
+ public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder)
+ {
+ FrameComponent[] srcComponents = decoder.Frame.Components;
+
+ ComponentData[] destComponents = new ComponentData[srcComponents.Length];
+ throw new NotImplementedException();
+ }
+ }
+
+ public class ComponentData
+ {
+ public ComponentData(int yCount, int xCount, int index)
+ {
+ this.YCount = yCount;
+ this.XCount = xCount;
+ this.Index = index;
+ this.Blocks = new Block[this.YCount, this.XCount];
+ }
+
+ public int Index { get; }
+
+ public int YCount { get; }
+
+ public int XCount { get; }
+
+ public Block[,] Blocks { get; private set; }
+
+ public short MinVal { get; private set; } = short.MaxValue;
+
+ public short MaxVal { get; private set; } = short.MinValue;
+
+ public static ComponentData Load(Array bloxSource, int index)
+ {
+ int yCount = bloxSource.Length;
+ Array row0 = (Array)bloxSource.GetValue(0);
+ int xCount = row0.Length;
+ ComponentData result = new ComponentData(yCount, xCount, index);
+ result.Init(bloxSource);
+ return result;
+ }
+
+ private void Init(Array bloxSource)
+ {
+ for (int i = 0; i < bloxSource.Length; i++)
+ {
+ Array row = (Array)bloxSource.GetValue(i);
+ for (int j = 0; j < row.Length; j++)
+ {
+ object jBlock = row.GetValue(j);
+ short[] data = (short[])GetNonPublicMember(jBlock, "data");
+ this.MinVal = Math.Min(this.MinVal, data.Min());
+ this.MaxVal = Math.Max(this.MaxVal, data.Max());
+ this.Blocks[i, j] = new Block(data);
+ }
+ }
+ }
+
+ public Image CreateGrayScaleImage()
+ {
+ Image result = new Image(this.XCount * 8, this.YCount * 8);
+
+ for (int by = 0; by < this.YCount; by++)
+ {
+ for (int bx = 0; bx < this.XCount; bx++)
+ {
+ WriteToImage(this.Blocks[by, bx], bx, by, result);
+ }
+ }
+ return result;
+ }
+
+ private void WriteToImage(Block block, int bx, int by, Image image)
+ {
+ float d = (this.MaxVal - this.MinVal);
+ for (int y = 0; y < 8; y++)
+ {
+ for (int x = 0; x < 8; x++)
+ {
+ int yy = by * 8 + y;
+ int xx = bx * 8 + x;
+ float val = block[x, y];
+ val -= this.MinVal;
+ val /= d;
+
+ Vector4 v = new Vector4(val, val, val, 1);
+ Rgba32 color = default(Rgba32);
+ color.PackFromVector4(v);
+
+ image[xx, yy] = color;
+ }
+ }
+ }
+ }
+
+ internal static FieldInfo GetNonPublicField(object obj, string fieldName)
+ {
+ Type type = obj.GetType();
+ return type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ }
+
+ internal static object GetNonPublicMember(object obj, string fieldName)
+ {
+ FieldInfo fi = GetNonPublicField(obj, fieldName);
+ return fi.GetValue(obj);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
new file mode 100644
index 0000000000..eeb879b8cc
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
@@ -0,0 +1,89 @@
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Tests
+{
+ using System.IO;
+
+ using SixLabors.ImageSharp.Formats;
+ using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
+ using SixLabors.ImageSharp.PixelFormats;
+
+ using Xunit;
+ using Xunit.Abstractions;
+
+ public class SpectralJpegTests
+ {
+ public SpectralJpegTests(ITestOutputHelper output)
+ {
+ this.Output = output;
+ }
+
+ private ITestOutputHelper Output { get; }
+
+ public static readonly string[] BaselineTestJpegs =
+ {
+ TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk,
+ TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Jpeg444,
+ TestImages.Jpeg.Baseline.Testimgorig,
+ TestImages.Jpeg.Baseline.Bad.BadEOF,
+ TestImages.Jpeg.Baseline.Bad.ExifUndefType,
+ };
+
+ public static readonly string[] ProgressiveTestJpegs =
+ {
+ TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress,
+ TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF
+ };
+
+ [Theory]
+ [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
+ public void BuildLibJpegSpectralResult(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
+
+ using (var ms = new MemoryStream(sourceBytes))
+ {
+ LibJpegTools.SpectralData data = LibJpegTools.SpectralData.Load(ms);
+ Assert.True(data.ComponentCount > 0);
+ this.Output.WriteLine($"ComponentCount: {data.ComponentCount}");
+
+ this.SaveSpectralImage(provider, data);
+ }
+ }
+
+ [Theory]
+ [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
+ public void JpegDecoderCore_ParseStream_SaveSpectralResult(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ JpegDecoderCore decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
+
+ byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
+
+ using (var ms = new MemoryStream(sourceBytes))
+ {
+ decoder.ParseStream(ms);
+
+ var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
+ this.SaveSpectralImage(provider, data);
+ }
+ }
+
+ private void SaveSpectralImage(TestImageProvider provider, LibJpegTools.SpectralData data)
+ where TPixel : struct, IPixel
+ {
+ foreach (LibJpegTools.ComponentData comp in data.Components)
+ {
+ this.Output.WriteLine("Min: " + comp.MinVal);
+ this.Output.WriteLine("MAx: " + comp.MaxVal);
+
+ using (Image image = comp.CreateGrayScaleImage())
+ {
+ string details = $"C{comp.Index}";
+ image.DebugSave(provider, details, appendPixelTypeToFileName: false);
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
index e8a6e8c596..f936fa9841 100644
--- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
+++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
@@ -16,6 +16,7 @@
+