Browse Source

Merge branch 'master' into bitconverter-uint64-readfix

af/merge-core
Johannes Bildstein 9 years ago
committed by GitHub
parent
commit
816d2fe114
  1. 90
      README.md
  2. 6
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  3. 46
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  4. 6
      src/ImageSharp/Common/Extensions/SimdUtils.cs
  5. 11
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  6. 98
      src/ImageSharp/Common/Tuples/Tuple8.cs
  7. 75
      src/ImageSharp/Common/Tuples/Vector4Pair.cs
  8. 9
      src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs
  9. 146
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs
  10. 166
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs
  11. 60
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt
  12. 296
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  13. 155
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
  14. 70
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
  15. 7
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs
  16. 7
      src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs
  17. 24
      src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs
  18. 4
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs
  19. 6
      src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
  20. 52
      src/ImageSharp/Image/ImageExtensions.cs
  21. 2
      src/ImageSharp/ImageSharp.csproj
  22. 2
      src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
  23. 38
      tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs
  24. 40
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  25. 108
      tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs
  26. 189
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  27. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  28. 126
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  29. 10
      tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
  30. 21
      tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs
  31. 80
      tests/ImageSharp.Tests/Image/ImageSaveTests.cs
  32. 2
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  33. 2
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  34. 21
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs
  35. 13
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
  36. 10
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  37. 24
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

90
README.md

@ -1,43 +1,29 @@
# <img src="https://raw.githubusercontent.com/SixLabors/ImageSharp/master/build/icons/imagesharp-logo-256.png" alt="ImageSharp" width="52"/> ImageSharp
**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`.
Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
> **ImageSharp** has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status.
>
> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/sixlabors).
[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt)
[![GitHub issues](https://img.shields.io/github/issues/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/issues)
[![GitHub stars](https://img.shields.io/github/stars/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/network)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
[![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors)
# <img src="https://raw.githubusercontent.com/SixLabors/ImageSharp/master/build/icons/imagesharp-logo-256.png" alt="ImageSharp" width="52"/> ImageSharp
**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments.
| |Build Status|Code Coverage|
|-------------|:----------:|:-----------:|
|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)|
|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)|
Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
### Questions?
### Installation
At present the code is pre-release but when ready it will be available on [Nuget](http://www.nuget.org).
Do you have questions? We are happy to help! Please [join our gitter channel](https://gitter.im/ImageSharp/General), or ask them on [stackoverflow](https://stackoverflow.com) using the `ImageSharp` tag.
**Pre-release downloads**
### Installation
We already have a [MyGet package repository](https://www.myget.org/gallery/sixlabors) - for bleeding-edge / development NuGet releases.
| Package Name | Release (NuGet) | Nightly (MyGet) |
|--------------------------------|-----------------|-----------------|
| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) |
| `SixLabors.ImageSharp.Drawing` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Drawing.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Drawing/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Drawing.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Drawing) |
### Packages
The **ImageSharp** library is made up of multiple packages.
Packages include:
The **ImageSharp** library is made up of multiple packages:
- **SixLabors.ImageSharp**
- Contains the generic `Image<TPixel>` class, PixelFormats, Primitives, Configuration, and other core functionality.
- The `IImageFormat` interface, Jpeg, Png, Bmp, and Gif formats.
@ -49,23 +35,12 @@ Packages include:
- Various vector drawing methods for drawing paths, polygons etc.
- Text drawing.
### Manual build
If you prefer, you can compile ImageSharp yourself (please do and help!), you'll need:
- [Visual Studio 2017 (or above)](https://www.visualstudio.com/en-us/news/releasenotes/vs2017-relnotes)
- The [.NET Core SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link.
### Build Status
Alternatively on Linux you can use:
- [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp)
- [.Net Core](https://www.microsoft.com/net/core#linuxubuntu)
To clone it locally click the "Clone in Windows" button above or run the following git commands.
```bash
git clone https://github.com/SixLabors/ImageSharp
```
| |Build Status|Code Coverage|
|-------------|:----------:|:-----------:|
|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)|
|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)|
### Features
@ -73,16 +48,8 @@ There's plenty there and more coming. Check out the [current features](features.
### API
Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks.
Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments.
Many `Image<TPixel>` methods are also fluent.
Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix.
`Rgba32` is our default PixelFormat, equivalent to `System.Drawing Color`.
On platforms supporting netstandard 1.3+
```csharp
// Image.Load(string path) is a shortcut for our default type. Other pixel formats use Image.Load<TPixel>(string path))
@ -118,14 +85,29 @@ using (Image<Rgba32> image = new Image<Rgba32>(400, 400))
}
```
For optimized synchronous access within a loop it is recommended that the following methods are used.
`Rgba32` is our default PixelFormat, equivalent to `System.Drawing Color`. For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
1. `image.GetRowSpan(y)`
2. `image.GetRowSpan(x, y)`
All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start.
For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
**Check out [this blog post](https://sixlabors.com/blog/announcing-imagesharp-beta-1/) or our [Samples Repository](https://github.com/SixLabors/Samples/tree/master/ImageSharp) for more examples!**
All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start.
### Manual build
If you prefer, you can compile ImageSharp yourself (please do and help!), you'll need:
- [Visual Studio 2017 (or above)](https://www.visualstudio.com/en-us/news/releasenotes/vs2017-relnotes)
- The [.NET Core SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link.
Alternatively on Linux you can use:
- [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp)
- [.Net Core](https://www.microsoft.com/net/core#linuxubuntu)
To clone it locally click the "Clone in Windows" button above or run the following git commands.
```bash
git clone https://github.com/SixLabors/ImageSharp
```
### How can you help?

6
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -37,9 +37,9 @@
<ProjectReference Include="..\ImageSharp\ImageSharp.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0002" />
<PackageReference Include="SixLabors.Shapes.Text" Version="1.0.0-beta0001" />
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0001" />
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0003" />
<PackageReference Include="SixLabors.Shapes.Text" Version="1.0.0-beta0002" />
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0002" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
<PrivateAssets>All</PrivateAssets>
</PackageReference>

46
src/ImageSharp/Advanced/ImageExtensions.cs → src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -10,15 +10,39 @@ namespace SixLabors.ImageSharp.Advanced
/// <summary>
/// Extension methods over Image{TPixel}
/// </summary>
internal static class ImageExtensions
public static class AdvancedImageExtensions
{
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contigous memory area containing Width*Height TPixel elements layed out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image frame</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource<TPixel>)source);
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contigous memory area containing Width*Height TPixel elements layed out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();
/// <summary>
/// Gets the representation of the pixels as an area of contiguous memory in the given pixel format.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
internal static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> GetSpan(source);
@ -29,7 +53,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int row)
internal static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int row)
where TPixel : struct, IPixel<TPixel>
=> GetSpan(source, row);
@ -39,7 +63,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
internal static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelSpan();
@ -50,7 +74,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int row)
internal static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int row)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowSpan(row);
@ -60,7 +84,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <returns>Returns the configuration.</returns>
public static Configuration GetConfiguration<TPixel>(this Image<TPixel> source)
internal static Configuration GetConfiguration<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> GetConfiguration((IConfigurable)source);
@ -107,5 +131,15 @@ namespace SixLabors.ImageSharp.Advanced
/// <returns>Returns the bounds of the image</returns>
private static Configuration GetConfiguration(IConfigurable source)
=> source?.Configuration ?? Configuration.Default;
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer.
/// Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
/// <param name="source">The source image frame</param>
/// <returns>A reference to the element.</returns>
private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref source.PixelBuffer.Span.DangerousGetPinnableReference();
}
}

6
src/ImageSharp/Common/Extensions/SimdUtils.cs

@ -15,13 +15,13 @@ namespace SixLabors.ImageSharp
internal static class SimdUtils
{
/// <summary>
/// Indicates AVX2 architecture where both float and integer registers are of size 256 byte.
/// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte.
/// </summary>
public static readonly bool IsAvx2 = Vector<float>.Count == 8 && Vector<int>.Count == 8;
public static bool IsAvx2CompatibleArchitecture => Vector<float>.Count == 8 && Vector<int>.Count == 8;
internal static void GuardAvx2(string operation)
{
if (!IsAvx2)
if (!IsAvx2CompatibleArchitecture)
{
throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!");
}

11
src/ImageSharp/Common/Extensions/StreamExtensions.cs

@ -32,7 +32,16 @@ namespace SixLabors.ImageSharp
byte[] foo = ArrayPool<byte>.Shared.Rent(count);
try
{
stream.Read(foo, 0, count);
while (count > 0)
{
int bytesRead = stream.Read(foo, 0, count);
if (bytesRead == 0)
{
break;
}
count -= bytesRead;
}
}
finally
{

98
src/ImageSharp/Common/Tuples/Tuple8.cs

@ -0,0 +1,98 @@
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Common.Tuples
{
/// <summary>
/// Contains value type tuples of 8 elements.
/// TODO: We should T4 this stuff to be DRY
/// </summary>
internal static class Tuple8
{
/// <summary>
/// Value type tuple of 8 <see cref="uint"/>-s
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))]
public struct OfUInt32
{
[FieldOffset(0 * sizeof(uint))]
public uint V0;
[FieldOffset(1 * sizeof(uint))]
public uint V1;
[FieldOffset(2 * sizeof(uint))]
public uint V2;
[FieldOffset(3 * sizeof(uint))]
public uint V3;
[FieldOffset(4 * sizeof(uint))]
public uint V4;
[FieldOffset(5 * sizeof(uint))]
public uint V5;
[FieldOffset(6 * sizeof(uint))]
public uint V6;
[FieldOffset(7 * sizeof(uint))]
public uint V7;
public override string ToString()
{
return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]";
}
}
/// <summary>
/// Value type tuple of 8 <see cref="byte"/>-s
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct OfByte
{
[FieldOffset(0)]
public byte V0;
[FieldOffset(1)]
public byte V1;
[FieldOffset(2)]
public byte V2;
[FieldOffset(3)]
public byte V3;
[FieldOffset(4)]
public byte V4;
[FieldOffset(5)]
public byte V5;
[FieldOffset(6)]
public byte V6;
[FieldOffset(7)]
public byte V7;
public override string ToString()
{
return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]";
}
/// <summary>
/// Sets the values of this tuple by casting all elements of the given <see cref="OfUInt32"/> tuple to <see cref="byte"/>.
/// </summary>
public void LoadFrom(ref OfUInt32 i)
{
this.V0 = (byte)i.V0;
this.V1 = (byte)i.V1;
this.V2 = (byte)i.V2;
this.V3 = (byte)i.V3;
this.V4 = (byte)i.V4;
this.V5 = (byte)i.V5;
this.V6 = (byte)i.V6;
this.V7 = (byte)i.V7;
}
}
}
}

75
src/ImageSharp/Common/Tuples/Vector4Pair.cs

@ -0,0 +1,75 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Common.Tuples
{
/// <summary>
/// Its faster to process multiple Vector4-s together, so let's pair them!
/// On AVX2 this pair should be convertible to <see cref="Vector{T}"/> of <see cref="float"/>!
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct Vector4Pair
{
public Vector4 A;
public Vector4 B;
private static readonly Vector4 Scale = new Vector4(1 / 255f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyInplace(float value)
{
this.A *= value;
this.B *= value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddInplace(Vector4 value)
{
this.A += value;
this.B += value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddInplace(ref Vector4Pair other)
{
this.A += other.A;
this.B += other.B;
}
/// <summary>
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4!
/// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscalePreAvx2()
{
ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A);
a = a.FastRound();
ref Vector<float> b = ref Unsafe.As<Vector4, Vector<float>>(ref this.B);
b = b.FastRound();
// Downscale by 1/255
this.A *= Scale;
this.B *= Scale;
}
/// <summary>
/// AVX2-only Downscale method, specific to Jpeg color conversion.
/// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscaleAvx2()
{
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this);
Vector<float> v = self;
v = v.FastRound();
// Downscale by 1/255
v *= new Vector<float>(1 / 255f);
self = v;
}
}
}

9
src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs

@ -176,17 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
/// <summary>
/// Convert into <see cref="Block8x8F"/>
/// Convert to <see cref="Block8x8F"/>
/// </summary>
public Block8x8F AsFloatBlock()
{
// TODO: Optimize this
var result = default(Block8x8F);
for (int i = 0; i < Size; i++)
{
result[i] = this[i];
}
result.LoadFrom(ref this);
return result;
}

146
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs

@ -0,0 +1,146 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
internal partial struct Block8x8F
{
/// <summary>
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical.
/// </summary>
public void CopyTo(BufferArea<float> area, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
this.CopyTo(area);
return;
}
else if (horizontalScale == 2 && verticalScale == 2)
{
this.CopyTo2x2(area);
return;
}
// TODO: Optimize: implement all the cases with loopless special code! (T4?)
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
int y8 = y * 8;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
float value = this[y8 + x];
for (int i = 0; i < verticalScale; i++)
{
for (int j = 0; j < horizontalScale; j++)
{
area[xx + j, yy + i] = value;
}
}
}
}
}
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyTo(BufferArea<float> area)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref area.GetReferenceToOrigo());
int destStride = area.Stride * sizeof(float);
CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
CopyRowImpl(ref selfBase, ref destBase, destStride, 2);
CopyRowImpl(ref selfBase, ref destBase, destStride, 3);
CopyRowImpl(ref selfBase, ref destBase, destStride, 4);
CopyRowImpl(ref selfBase, ref destBase, destStride, 5);
CopyRowImpl(ref selfBase, ref destBase, destStride, 6);
CopyRowImpl(ref selfBase, ref destBase, destStride, 7);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
{
ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
private void CopyTo2x2(BufferArea<float> area)
{
ref float destBase = ref area.GetReferenceToOrigo();
int destStride = area.Stride;
this.WidenCopyImpl2x2(ref destBase, 0, destStride);
this.WidenCopyImpl2x2(ref destBase, 1, destStride);
this.WidenCopyImpl2x2(ref destBase, 2, destStride);
this.WidenCopyImpl2x2(ref destBase, 3, destStride);
this.WidenCopyImpl2x2(ref destBase, 4, destStride);
this.WidenCopyImpl2x2(ref destBase, 5, destStride);
this.WidenCopyImpl2x2(ref destBase, 6, destStride);
this.WidenCopyImpl2x2(ref destBase, 7, destStride);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WidenCopyImpl2x2(ref float destBase, int row, int destStride)
{
ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row);
ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1);
ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride);
Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X;
Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X;
Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y;
Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y;
Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z;
Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z;
Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W;
Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 0) = selfLeft.X;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 1) = selfLeft.X;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 2) = selfLeft.Y;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 3) = selfLeft.Y;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 4) = selfLeft.Z;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 5) = selfLeft.Z;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 6) = selfLeft.W;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 7) = selfLeft.W;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 0) = selfRight.X;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 1) = selfRight.X;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 2) = selfRight.Y;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 3) = selfRight.Y;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 4) = selfRight.Z;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 5) = selfRight.Z;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 6) = selfRight.W;
Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 7) = selfRight.W;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WidenCopyImpl(ref Vector4 s, ref float destBase)
{
Unsafe.Add(ref destBase, 0) = s.X;
Unsafe.Add(ref destBase, 1) = s.X;
Unsafe.Add(ref destBase, 2) = s.Y;
Unsafe.Add(ref destBase, 3) = s.Y;
Unsafe.Add(ref destBase, 4) = s.Z;
Unsafe.Add(ref destBase, 5) = s.Z;
Unsafe.Add(ref destBase, 6) = s.W;
Unsafe.Add(ref destBase, 7) = s.W;
}
}
}

166
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs

@ -96,51 +96,139 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// </summary>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void NormalizeColorsInto(ref Block8x8F d)
public void NormalizeColorsInplace()
{
d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4);
d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4);
d.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4);
d.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4);
d.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4);
d.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4);
d.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4);
d.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4);
d.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4);
d.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4);
d.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4);
d.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4);
d.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4);
d.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4);
d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4);
d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4);
this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4);
this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4);
this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4);
this.V1R = Vector4.Clamp(this.V1R + COff4, CMin4, CMax4);
this.V2L = Vector4.Clamp(this.V2L + COff4, CMin4, CMax4);
this.V2R = Vector4.Clamp(this.V2R + COff4, CMin4, CMax4);
this.V3L = Vector4.Clamp(this.V3L + COff4, CMin4, CMax4);
this.V3R = Vector4.Clamp(this.V3R + COff4, CMin4, CMax4);
this.V4L = Vector4.Clamp(this.V4L + COff4, CMin4, CMax4);
this.V4R = Vector4.Clamp(this.V4R + COff4, CMin4, CMax4);
this.V5L = Vector4.Clamp(this.V5L + COff4, CMin4, CMax4);
this.V5R = Vector4.Clamp(this.V5R + COff4, CMin4, CMax4);
this.V6L = Vector4.Clamp(this.V6L + COff4, CMin4, CMax4);
this.V6R = Vector4.Clamp(this.V6R + COff4, CMin4, CMax4);
this.V7L = Vector4.Clamp(this.V7L + COff4, CMin4, CMax4);
this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4);
}
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// AVX2-only variant for executing <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void NormalizeColorsInplace()
public void NormalizeColorsAndRoundInplaceAvx2()
{
Vector<float> off = new Vector<float>(128f);
Vector<float> max = new Vector<float>(255F);
ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V0L);
row0 = NormalizeAndRound(row0, off, max);
ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V1L);
row1 = NormalizeAndRound(row1, off, max);
ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V2L);
row2 = NormalizeAndRound(row2, off, max);
ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V3L);
row3 = NormalizeAndRound(row3, off, max);
ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V4L);
row4 = NormalizeAndRound(row4, off, max);
ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V5L);
row5 = NormalizeAndRound(row5, off, max);
ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V6L);
row6 = NormalizeAndRound(row6, off, max);
ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V7L);
row7 = NormalizeAndRound(row7, off, max);
}
/// <summary>
/// Fill the block from 'source' doing short -> float conversion.
/// </summary>
public void LoadFrom(ref Block8x8 source)
{
this.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4);
this.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4);
this.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4);
this.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4);
this.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4);
this.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4);
this.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4);
this.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4);
this.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4);
this.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4);
this.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4);
this.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4);
this.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4);
this.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4);
this.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4);
this.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4);
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref source);
this.V0L.X = Unsafe.Add(ref selfRef, 0);
this.V0L.Y = Unsafe.Add(ref selfRef, 1);
this.V0L.Z = Unsafe.Add(ref selfRef, 2);
this.V0L.W = Unsafe.Add(ref selfRef, 3);
this.V0R.X = Unsafe.Add(ref selfRef, 4);
this.V0R.Y = Unsafe.Add(ref selfRef, 5);
this.V0R.Z = Unsafe.Add(ref selfRef, 6);
this.V0R.W = Unsafe.Add(ref selfRef, 7);
this.V1L.X = Unsafe.Add(ref selfRef, 8);
this.V1L.Y = Unsafe.Add(ref selfRef, 9);
this.V1L.Z = Unsafe.Add(ref selfRef, 10);
this.V1L.W = Unsafe.Add(ref selfRef, 11);
this.V1R.X = Unsafe.Add(ref selfRef, 12);
this.V1R.Y = Unsafe.Add(ref selfRef, 13);
this.V1R.Z = Unsafe.Add(ref selfRef, 14);
this.V1R.W = Unsafe.Add(ref selfRef, 15);
this.V2L.X = Unsafe.Add(ref selfRef, 16);
this.V2L.Y = Unsafe.Add(ref selfRef, 17);
this.V2L.Z = Unsafe.Add(ref selfRef, 18);
this.V2L.W = Unsafe.Add(ref selfRef, 19);
this.V2R.X = Unsafe.Add(ref selfRef, 20);
this.V2R.Y = Unsafe.Add(ref selfRef, 21);
this.V2R.Z = Unsafe.Add(ref selfRef, 22);
this.V2R.W = Unsafe.Add(ref selfRef, 23);
this.V3L.X = Unsafe.Add(ref selfRef, 24);
this.V3L.Y = Unsafe.Add(ref selfRef, 25);
this.V3L.Z = Unsafe.Add(ref selfRef, 26);
this.V3L.W = Unsafe.Add(ref selfRef, 27);
this.V3R.X = Unsafe.Add(ref selfRef, 28);
this.V3R.Y = Unsafe.Add(ref selfRef, 29);
this.V3R.Z = Unsafe.Add(ref selfRef, 30);
this.V3R.W = Unsafe.Add(ref selfRef, 31);
this.V4L.X = Unsafe.Add(ref selfRef, 32);
this.V4L.Y = Unsafe.Add(ref selfRef, 33);
this.V4L.Z = Unsafe.Add(ref selfRef, 34);
this.V4L.W = Unsafe.Add(ref selfRef, 35);
this.V4R.X = Unsafe.Add(ref selfRef, 36);
this.V4R.Y = Unsafe.Add(ref selfRef, 37);
this.V4R.Z = Unsafe.Add(ref selfRef, 38);
this.V4R.W = Unsafe.Add(ref selfRef, 39);
this.V5L.X = Unsafe.Add(ref selfRef, 40);
this.V5L.Y = Unsafe.Add(ref selfRef, 41);
this.V5L.Z = Unsafe.Add(ref selfRef, 42);
this.V5L.W = Unsafe.Add(ref selfRef, 43);
this.V5R.X = Unsafe.Add(ref selfRef, 44);
this.V5R.Y = Unsafe.Add(ref selfRef, 45);
this.V5R.Z = Unsafe.Add(ref selfRef, 46);
this.V5R.W = Unsafe.Add(ref selfRef, 47);
this.V6L.X = Unsafe.Add(ref selfRef, 48);
this.V6L.Y = Unsafe.Add(ref selfRef, 49);
this.V6L.Z = Unsafe.Add(ref selfRef, 50);
this.V6L.W = Unsafe.Add(ref selfRef, 51);
this.V6R.X = Unsafe.Add(ref selfRef, 52);
this.V6R.Y = Unsafe.Add(ref selfRef, 53);
this.V6R.Z = Unsafe.Add(ref selfRef, 54);
this.V6R.W = Unsafe.Add(ref selfRef, 55);
this.V7L.X = Unsafe.Add(ref selfRef, 56);
this.V7L.Y = Unsafe.Add(ref selfRef, 57);
this.V7L.Z = Unsafe.Add(ref selfRef, 58);
this.V7L.W = Unsafe.Add(ref selfRef, 59);
this.V7R.X = Unsafe.Add(ref selfRef, 60);
this.V7R.Y = Unsafe.Add(ref selfRef, 61);
this.V7R.Z = Unsafe.Add(ref selfRef, 62);
this.V7R.W = Unsafe.Add(ref selfRef, 63);
}
}
}
}

60
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt

@ -61,9 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// </summary>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
public void NormalizeColorsInplace()
{
<#
@ -74,7 +72,61 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
for (int j = 0; j < 2; j++)
{
char side = j == 0 ? 'L' : 'R';
Write($"d.V{i}{side} = Vector4.Clamp(V{i}{side} + COff4, CMin4, CMax4);\r\n");
Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n");
}
}
PopIndent();
#>
}
/// <summary>
/// AVX2-only variant for executing <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void NormalizeColorsAndRoundInplaceAvx2()
{
Vector<float> off = new Vector<float>(128f);
Vector<float> max = new Vector<float>(255F);
<#
for (int i = 0; i < 8; i++)
{
#>
ref Vector<float> row<#=i#> = ref Unsafe.As<Vector4, Vector<float>>(ref this.V<#=i#>L);
row<#=i#> = NormalizeAndRound(row<#=i#>, off, max);
<#
}
#>
}
/// <summary>
/// Fill the block from 'source' doing short -> float conversion.
/// </summary>
public void LoadFrom(ref Block8x8 source)
{
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref source);
<#
PushIndent(" ");
for (int j = 0; j < 8; j++)
{
for (int i = 0; i < 8; i++)
{
char destCoord = coordz[i % 4];
char destSide = (i / 4) % 2 == 0 ? 'L' : 'R';
if(j > 0 && i == 0){
WriteLine("");
}
char srcCoord = coordz[j % 4];
char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R';
string expression = $"this.V{j}{destSide}.{destCoord} = Unsafe.Add(ref selfRef, {j*8+i});\r\n";
Write(expression);
}
}
PopIndent();

296
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs

@ -7,7 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
@ -17,16 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// </summary>
internal partial struct Block8x8F
{
// Most of the static methods of this struct are instance methods by actual semantics: they use Block8x8F* as their first parameter.
// Example: GetScalarAt() and SetScalarAt() are really just other (optimized) versions of the indexer.
// It's much cleaner, easier and safer to work with the code, if the methods with same semantics are next to each other.
#pragma warning disable SA1204 // StaticElementsMustAppearBeforeInstanceElements
/// <summary>
/// Vector count
/// </summary>
public const int VectorCount = 16;
/// <summary>
/// A number of scalar coefficients in a <see cref="Block8x8F"/>
/// </summary>
@ -157,36 +146,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return result;
}
/// <summary>
/// Pointer-based "Indexer" (getter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <returns>The scaleVec value at the specified index</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
{
GuardBlockIndex(idx);
float* fp = (float*)blockPtr;
return fp[idx];
}
/// <summary>
/// Pointer-based "Indexer" (setter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <param name="value">Value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
{
GuardBlockIndex(idx);
float* fp = (float*)blockPtr;
fp[idx] = value;
}
/// <summary>
/// Fill the block with defaults (zeroes)
/// </summary>
@ -242,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// </summary>
/// <param name="dest">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyTo(Span<float> dest)
public void CopyTo(Span<float> dest)
{
ref byte d = ref Unsafe.As<float, byte>(ref dest.DangerousGetPinnableReference());
ref byte s = ref Unsafe.As<Block8x8F, byte>(ref this);
@ -306,109 +265,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyTo(BufferArea<float> area)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref area.GetReferenceToOrigo());
int destStride = area.Stride * sizeof(float);
CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
CopyRowImpl(ref selfBase, ref destBase, destStride, 2);
CopyRowImpl(ref selfBase, ref destBase, destStride, 3);
CopyRowImpl(ref selfBase, ref destBase, destStride, 4);
CopyRowImpl(ref selfBase, ref destBase, destStride, 5);
CopyRowImpl(ref selfBase, ref destBase, destStride, 6);
CopyRowImpl(ref selfBase, ref destBase, destStride, 7);
}
public void CopyTo(BufferArea<float> area, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
this.CopyTo(area);
return;
}
else if (horizontalScale == 2 && verticalScale == 2)
{
this.CopyTo2x2(area);
return;
}
// TODO: Optimize: implement all the cases with loopless special code! (T4?)
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
int y8 = y * 8;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
float value = this[y8 + x];
for (int i = 0; i < verticalScale; i++)
{
for (int j = 0; j < horizontalScale; j++)
{
area[xx + j, yy + i] = value;
}
}
}
}
}
private void CopyTo2x2(BufferArea<float> area)
{
ref float destBase = ref area.GetReferenceToOrigo();
int destStride = area.Stride;
this.CopyRow2x2Impl(ref destBase, 0, destStride);
this.CopyRow2x2Impl(ref destBase, 1, destStride);
this.CopyRow2x2Impl(ref destBase, 2, destStride);
this.CopyRow2x2Impl(ref destBase, 3, destStride);
this.CopyRow2x2Impl(ref destBase, 4, destStride);
this.CopyRow2x2Impl(ref destBase, 5, destStride);
this.CopyRow2x2Impl(ref destBase, 6, destStride);
this.CopyRow2x2Impl(ref destBase, 7, destStride);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
{
ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyRow2x2Impl(ref float destBase, int row, int destStride)
{
ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row);
ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1);
ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride);
Stride2VectorCopyImpl(ref selfLeft, ref destLocalOrigo);
Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, 8));
Stride2VectorCopyImpl(ref selfLeft, ref Unsafe.Add(ref destLocalOrigo, destStride));
Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, destStride + 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Stride2VectorCopyImpl(ref Vector4 s, ref float destBase)
{
Unsafe.Add(ref destBase, 0) = s.X;
Unsafe.Add(ref destBase, 1) = s.X;
Unsafe.Add(ref destBase, 2) = s.Y;
Unsafe.Add(ref destBase, 3) = s.Y;
Unsafe.Add(ref destBase, 4) = s.Z;
Unsafe.Add(ref destBase, 5) = s.Z;
Unsafe.Add(ref destBase, 6) = s.W;
Unsafe.Add(ref destBase, 7) = s.W;
}
public float[] ToArray()
{
float[] result = new float[Size];
@ -419,26 +275,50 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <summary>
/// Multiply all elements of the block.
/// </summary>
/// <param name="scaleVec">Vector to multiply by</param>
/// <param name="value">The value to multiply by</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyAllInplace(float scaleVec)
{
this.V0L *= scaleVec;
this.V0R *= scaleVec;
this.V1L *= scaleVec;
this.V1R *= scaleVec;
this.V2L *= scaleVec;
this.V2R *= scaleVec;
this.V3L *= scaleVec;
this.V3R *= scaleVec;
this.V4L *= scaleVec;
this.V4R *= scaleVec;
this.V5L *= scaleVec;
this.V5R *= scaleVec;
this.V6L *= scaleVec;
this.V6R *= scaleVec;
this.V7L *= scaleVec;
this.V7R *= scaleVec;
public void MultiplyInplace(float value)
{
this.V0L *= value;
this.V0R *= value;
this.V1L *= value;
this.V1R *= value;
this.V2L *= value;
this.V2R *= value;
this.V3L *= value;
this.V3R *= value;
this.V4L *= value;
this.V4R *= value;
this.V5L *= value;
this.V5R *= value;
this.V6L *= value;
this.V6R *= value;
this.V7L *= value;
this.V7R *= value;
}
/// <summary>
/// Multiply all elements of the block by the corresponding elements of 'other'
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyInplace(ref Block8x8F other)
{
this.V0L *= other.V0L;
this.V0R *= other.V0R;
this.V1L *= other.V1L;
this.V1R *= other.V1R;
this.V2L *= other.V2L;
this.V2R *= other.V2R;
this.V3L *= other.V3L;
this.V3R *= other.V3R;
this.V4L *= other.V4L;
this.V4R *= other.V4R;
this.V5L *= other.V5L;
this.V5R *= other.V5R;
this.V6L *= other.V6L;
this.V6R *= other.V6R;
this.V7L *= other.V7L;
this.V7R *= other.V7R;
}
/// <summary>
@ -472,56 +352,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <param name="blockPtr">Block pointer</param>
/// <param name="qtPtr">Qt pointer</param>
/// <param name="unzigPtr">Unzig pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
for (int zig = 0; zig < Size; zig++)
for (int qtIndex = 0; qtIndex < Size; qtIndex++)
{
float* unzigPos = b + unzigPtr[zig];
int blockIndex = unzigPtr[qtIndex];
float* unzigPos = b + blockIndex;
float val = *unzigPos;
val *= qtp[zig];
val *= qtp[qtIndex];
*unzigPos = val;
}
}
/// <summary>
/// Level shift by +128, clip to [0, 255], and write to buffer.
/// </summary>
/// <param name="destinationBuffer">Color buffer</param>
/// <param name="stride">Stride offset</param>
/// <param name="tempBlockPtr">Temp Block pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyColorsTo(Span<byte> destinationBuffer, int stride, Block8x8F* tempBlockPtr)
{
this.NormalizeColorsInto(ref *tempBlockPtr);
ref byte d = ref destinationBuffer.DangerousGetPinnableReference();
float* src = (float*)tempBlockPtr;
for (int i = 0; i < 8; i++)
{
ref byte dRow = ref Unsafe.Add(ref d, i * stride);
Unsafe.Add(ref dRow, 0) = (byte)src[0];
Unsafe.Add(ref dRow, 1) = (byte)src[1];
Unsafe.Add(ref dRow, 2) = (byte)src[2];
Unsafe.Add(ref dRow, 3) = (byte)src[3];
Unsafe.Add(ref dRow, 4) = (byte)src[4];
Unsafe.Add(ref dRow, 5) = (byte)src[5];
Unsafe.Add(ref dRow, 6) = (byte)src[6];
Unsafe.Add(ref dRow, 7) = (byte)src[7];
src += 8;
}
}
/// <summary>
/// Quantize 'block' into 'dest' using the 'qt' quantization table:
/// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values.
/// To finish the rounding it's enough to (int)-cast these values.
/// </summary>
/// <param name="block">Source block</param>
/// <param name="dest">Destination block</param>
/// <param name="qt">The quantization table</param>
/// <param name="unzigPtr">Pointer to elements of <see cref="UnzigData"/></param>
public static unsafe void UnzigDivRound(
/// <param name="unzigPtr">Pointer to elements of <see cref="ZigZag"/></param>
public static unsafe void Quantize(
Block8x8F* block,
Block8x8F* dest,
Block8x8F* qt,
@ -610,34 +466,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return result;
}
public void RoundInplace()
/// <summary>
/// Level shift by +128, clip to [0..255], and round all the values in the block.
/// </summary>
public void NormalizeColorsAndRoundInplace()
{
if (Vector<float>.Count == 8 && Vector<int>.Count == 8)
if (SimdUtils.IsAvx2CompatibleArchitecture)
{
ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V0L);
row0 = row0.FastRound();
ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V1L);
row1 = row1.FastRound();
ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V2L);
row2 = row2.FastRound();
ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V3L);
row3 = row3.FastRound();
ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V4L);
row4 = row4.FastRound();
ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V5L);
row5 = row5.FastRound();
ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V6L);
row6 = row6.FastRound();
ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V7L);
row7 = row7.FastRound();
this.NormalizeColorsAndRoundInplaceAvx2();
}
else
{
this.RoundInplaceSlow();
this.NormalizeColorsInplace();
this.RoundInplace();
}
}
private void RoundInplaceSlow()
/// <summary>
/// Rounds all values in the block.
/// </summary>
public void RoundInplace()
{
for (int i = 0; i < Size; i++)
{
@ -663,6 +511,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return bld.ToString();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector<float> NormalizeAndRound(Vector<float> row, Vector<float> off, Vector<float> max)
{
row += off;
row = Vector.Max(row, Vector<float>.Zero);
row = Vector.Min(row, max);
return row.FastRound();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
@ -679,10 +536,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
DebugGuard.MustBeLessThan(idx, Size, nameof(idx));
DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx));
}
[StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))]
private struct Row
{
}
}
}

155
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs

@ -11,145 +11,72 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// Encapsulates the implementation of processing "raw" <see cref="Buffer{T}"/>-s into Jpeg image channels.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct JpegBlockPostProcessor
internal struct JpegBlockPostProcessor
{
/// <summary>
/// The <see cref="ComputationData"/>
/// Source block
/// </summary>
private ComputationData data;
public Block8x8F SourceBlock;
/// <summary>
/// Pointers to elements of <see cref="data"/>
/// Temporal block 1 to store intermediate and/or final computation results
/// </summary>
private DataPointers pointers;
public Block8x8F WorkspaceBlock1;
/// <summary>
/// Initialize the <see cref="JpegBlockPostProcessor"/> instance on the stack.
/// Temporal block 2 to store intermediate and/or final computation results
/// </summary>
/// <param name="postProcessor">The <see cref="JpegBlockPostProcessor"/> instance</param>
public static void Init(JpegBlockPostProcessor* postProcessor)
{
postProcessor->data = ComputationData.Create();
postProcessor->pointers = new DataPointers(&postProcessor->data);
}
public void QuantizeAndTransform(IRawJpegData decoder, IJpegComponent component, ref Block8x8 sourceBlock)
{
this.data.SourceBlock = sourceBlock.AsFloatBlock();
int qtIndex = component.QuantizationTableIndex;
this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex];
Block8x8F* b = this.pointers.SourceBlock;
Block8x8F.DequantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig);
FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2);
}
public Block8x8F WorkspaceBlock2;
public void ProcessBlockColorsInto(
IRawJpegData decoder,
IJpegComponent component,
ref Block8x8 sourceBlock,
BufferArea<float> destArea)
{
this.QuantizeAndTransform(decoder, component, ref sourceBlock);
this.data.WorkspaceBlock1.NormalizeColorsInplace();
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// Unfortunately, we need to emulate this to be "more accurate" :(
this.data.WorkspaceBlock1.RoundInplace();
/// <summary>
/// The quantization table as <see cref="Block8x8F"/>
/// </summary>
public Block8x8F DequantiazationTable;
Size divs = component.SubSamplingDivisors;
this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height);
}
/// <summary>
/// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block.
/// </summary>
private Size subSamplingDivisors;
/// <summary>
/// Holds the "large" data blocks needed for computations.
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ComputationData
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{
/// <summary>
/// Source block
/// </summary>
public Block8x8F SourceBlock;
/// <summary>
/// Temporal block 1 to store intermediate and/or final computation results
/// </summary>
public Block8x8F WorkspaceBlock1;
/// <summary>
/// Temporal block 2 to store intermediate and/or final computation results
/// </summary>
public Block8x8F WorkspaceBlock2;
/// <summary>
/// The quantization table as <see cref="Block8x8F"/>
/// </summary>
public Block8x8F QuantiazationTable;
/// <summary>
/// The jpeg unzig data
/// </summary>
public UnzigData Unzig;
int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]);
this.subSamplingDivisors = component.SubSamplingDivisors;
/// <summary>
/// Creates and initializes a new <see cref="ComputationData"/> instance
/// </summary>
/// <returns>The <see cref="ComputationData"/></returns>
public static ComputationData Create()
{
var data = default(ComputationData);
data.Unzig = UnzigData.Create();
return data;
}
this.SourceBlock = default(Block8x8F);
this.WorkspaceBlock1 = default(Block8x8F);
this.WorkspaceBlock2 = default(Block8x8F);
}
/// <summary>
/// Contains pointers to the memory regions of <see cref="ComputationData"/> so they can be easily passed around to pointer based utility methods of <see cref="Block8x8F"/>
/// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
/// - Dequantize
/// - Applying IDCT
/// - Level shift by +128, clip to [0, 255]
/// - Copy the resultin color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>
/// </summary>
public struct DataPointers
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
BufferArea<float> destArea)
{
/// <summary>
/// Pointer to <see cref="ComputationData.SourceBlock"/>
/// </summary>
public Block8x8F* SourceBlock;
/// <summary>
/// Pointer to <see cref="ComputationData.WorkspaceBlock1"/>
/// </summary>
public Block8x8F* WorkspaceBlock1;
ref Block8x8F b = ref this.SourceBlock;
b.LoadFrom(ref sourceBlock);
/// <summary>
/// Pointer to <see cref="ComputationData.WorkspaceBlock2"/>
/// </summary>
public Block8x8F* WorkspaceBlock2;
// Dequantize:
b.MultiplyInplace(ref this.DequantiazationTable);
/// <summary>
/// Pointer to <see cref="ComputationData.QuantiazationTable"/>
/// </summary>
public Block8x8F* QuantiazationTable;
FastFloatingPointDCT.TransformIDCT(ref b, ref this.WorkspaceBlock1, ref this.WorkspaceBlock2);
/// <summary>
/// Pointer to <see cref="ComputationData.Unzig"/> as int*
/// </summary>
public int* Unzig;
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding!
this.WorkspaceBlock1.NormalizeColorsAndRoundInplace();
/// <summary>
/// Initializes a new instance of the <see cref="DataPointers" /> struct.
/// </summary>
/// <param name="dataPtr">Pointer to <see cref="ComputationData"/></param>
internal DataPointers(ComputationData* dataPtr)
{
this.SourceBlock = &dataPtr->SourceBlock;
this.WorkspaceBlock1 = &dataPtr->WorkspaceBlock1;
this.WorkspaceBlock2 = &dataPtr->WorkspaceBlock2;
this.QuantiazationTable = &dataPtr->QuantiazationTable;
this.Unzig = dataPtr->Unzig.Data;
}
this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height);
}
}
}

70
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs

@ -1,6 +1,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
@ -121,9 +122,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
tmp.MultiplyInplace(1.772F);
b.AddInplace(ref tmp);
r.RoundAndDownscale();
g.RoundAndDownscale();
b.RoundAndDownscale();
if (Vector<float>.Count == 4)
{
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?)
r.RoundAndDownscalePreAvx2();
g.RoundAndDownscalePreAvx2();
b.RoundAndDownscalePreAvx2();
}
else if (SimdUtils.IsAvx2CompatibleArchitecture)
{
r.RoundAndDownscaleAvx2();
g.RoundAndDownscaleAvx2();
b.RoundAndDownscaleAvx2();
}
else
{
// TODO: Run fallback scalar code here
// However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007
throw new NotImplementedException("Your CPU architecture is too modern!");
}
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
@ -131,53 +148,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
}
}
/// <summary>
/// Its faster to process multiple Vector4-s together
/// </summary>
private struct Vector4Pair
{
public Vector4 A;
public Vector4 B;
private static readonly Vector4 Scale = new Vector4(1 / 255f);
private static readonly Vector4 Half = new Vector4(0.5f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RoundAndDownscale()
{
// Emulate rounding:
this.A += Half;
this.B += Half;
// Downscale by 1/255
this.A *= Scale;
this.B *= Scale;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyInplace(float value)
{
this.A *= value;
this.B *= value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddInplace(Vector4 value)
{
this.A += value;
this.B += value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddInplace(ref Vector4Pair other)
{
this.A += other.A;
this.B += other.B;
}
}
private struct Vector4Octet
{
#pragma warning disable SA1132 // Do not combine fields

7
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs

@ -66,10 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary>
/// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>.
/// </summary>
public unsafe void CopyBlocksToColorBuffer()
public void CopyBlocksToColorBuffer()
{
var blockPp = default(JpegBlockPostProcessor);
JpegBlockPostProcessor.Init(&blockPp);
var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component);
for (int y = 0; y < this.BlockRowsPerStep; y++)
{
@ -95,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
this.blockAreaSize.Width,
this.blockAreaSize.Height);
blockPp.ProcessBlockColorsInto(this.ImagePostProcessor.RawJpeg, this.Component, ref block, destArea);
blockPp.ProcessBlockColorsInto(ref block, destArea);
}
}

7
src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs

@ -50,7 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <param name="temp">Temporary block provided by the caller</param>
public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp)
{
// TODO: Transpose is a bottleneck now. We need full AVX support to optimize it:
// https://github.com/dotnet/corefx/issues/22940
src.TransposeInto(ref temp);
IDCT8x4_LeftPart(ref temp, ref dest);
IDCT8x4_RightPart(ref temp, ref dest);
@ -60,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
IDCT8x4_RightPart(ref temp, ref dest);
// TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing?
dest.MultiplyAllInplace(C_0_125);
dest.MultiplyInplace(C_0_125);
}
/// <summary>
@ -334,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
dest.MultiplyAllInplace(C_0_125);
dest.MultiplyInplace(C_0_125);
}
}
}

24
src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs → src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs

@ -6,13 +6,12 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
/// <summary>
/// TODO: This should be simply just a <see cref="Block8x8"/>!
/// Holds the Jpeg UnZig array in a value/stack type.
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zigzag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
internal unsafe struct UnzigData
internal unsafe struct ZigZag
{
/// <summary>
/// Copy of <see cref="Unzig"/> in a value type
@ -33,15 +32,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
};
/// <summary>
/// Creates and fills an instance of <see cref="UnzigData"/> with Jpeg unzig indices
/// Creates and fills an instance of <see cref="ZigZag"/> with Jpeg unzig indices
/// </summary>
/// <returns>The new instance</returns>
public static UnzigData Create()
public static ZigZag CreateUnzigTable()
{
UnzigData result = default(UnzigData);
ZigZag result = default(ZigZag);
int* unzigPtr = result.Data;
Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
return result;
}
/// <summary>
/// Apply Zigging to the given quantization table, so it will be sufficient to multiply blocks for dequantizing them.
/// </summary>
public static Block8x8F CreateDequantizationTable(ref Block8x8F qt)
{
Block8x8F result = default(Block8x8F);
for (int i = 0; i < 64; i++)
{
result[Unzig[i]] = qt[i];
}
return result;
}
}
}

4
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// The jpeg unzig data
/// </summary>
public UnzigData Unzig;
public ZigZag Unzig;
/// <summary>
/// The buffer storing the <see cref="OrigComponentScan"/>-s for each component
@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public static ComputationData Create()
{
ComputationData data = default(ComputationData);
data.Unzig = UnzigData.Create();
data.Unzig = ZigZag.CreateUnzigTable();
return data;
}
}

6
src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs

@ -455,7 +455,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
UnzigData unzig = UnzigData.Create();
ZigZag unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@ -564,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2);
Block8x8F.UnzigDivRound(tempDest1, tempDest2, quant, unzigPtr);
Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr);
float* unziggedDestPtr = (float*)tempDest2;
int dc = (int)unziggedDestPtr[0];
@ -912,7 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
UnzigData unzig = UnzigData.Create();
ZigZag unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;

52
src/ImageSharp/Image/ImageExtensions.cs

@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Saves the raw image to the given bytes.
/// Saves the raw image pixels to a byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp
=> source.GetPixelSpan().AsBytes().ToArray();
/// <summary>
/// Saves the raw image to the given bytes.
/// Saves the raw image pixels to the given byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
@ -131,26 +131,21 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, byte[] buffer)
where TPixel : struct, IPixel<TPixel>
=> SavePixelData(source, new Span<byte>(buffer));
=> SavePixelData(source, buffer.AsSpan().NonPortableCast<byte, TPixel>());
/// <summary>
/// Saves the raw image to the given bytes.
/// Saves the raw image pixels to the given TPixel array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
private static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<byte> buffer)
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel>
{
Span<byte> byteBuffer = source.GetPixelSpan().AsBytes();
Guard.MustBeGreaterThanOrEqualTo(buffer.Length, byteBuffer.Length, nameof(buffer));
byteBuffer.CopyTo(buffer);
}
=> SavePixelData(source, new Span<TPixel>(buffer));
/// <summary>
/// Saves the raw image to the given bytes.
/// Saves the raw image pixels to a byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
@ -161,7 +156,7 @@ namespace SixLabors.ImageSharp
=> source.Frames.RootFrame.SavePixelData();
/// <summary>
/// Saves the raw image to the given bytes.
/// Saves the raw image pixels to the given byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
@ -172,13 +167,13 @@ namespace SixLabors.ImageSharp
=> source.Frames.RootFrame.SavePixelData(buffer);
/// <summary>
/// Saves the raw image to the given bytes.
/// Saves the raw image pixels to the given TPixel array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
private static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer)
public static void SavePixelData<TPixel>(this Image<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer);
@ -200,5 +195,32 @@ namespace SixLabors.ImageSharp
return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
}
}
/// <summary>
/// Saves the raw image to the given bytes.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
internal static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer.NonPortableCast<byte, TPixel>());
/// <summary>
/// Saves the raw image to the given bytes.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
internal static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<TPixel> buffer)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> sourceBuffer = source.GetPixelSpan();
Guard.MustBeGreaterThanOrEqualTo(buffer.Length, sourceBuffer.Length, nameof(buffer));
sourceBuffer.CopyTo(buffer);
}
}
}

2
src/ImageSharp/ImageSharp.csproj

@ -34,7 +34,7 @@
<Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0002" />
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0003" />
<AdditionalFiles Include="..\..\stylecop.json" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
<PrivateAssets>All</PrivateAssets>

2
src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs

@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp
{
GuardSpans(sourceVectors, nameof(sourceVectors), destColors, nameof(destColors), count);
if (!SimdUtils.IsAvx2)
if (!SimdUtils.IsAvx2CompatibleArchitecture)
{
base.PackFromVector4(sourceVectors, destColors, count);
return;

38
tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

@ -0,0 +1,38 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using Xunit;
using SixLabors.ImageSharp.Advanced;
using System.Runtime.CompilerServices;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Advanced
{
using SixLabors.ImageSharp.PixelFormats;
public class AdvancedImageExtensionsTests
{
[Theory]
[WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public unsafe void DangerousGetPinnableReference_CopyToBuffer<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
TPixel[] targetBuffer = new TPixel[image.Width * image.Height];
ref byte source = ref Unsafe.As<TPixel, byte>(ref targetBuffer[0]);
ref byte dest = ref Unsafe.As<TPixel, byte>(ref image.DangerousGetPinnableReferenceToPixelBuffer());
fixed (byte* targetPtr = &source)
fixed (byte* pixelBasePtr = &dest)
{
uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf<TPixel>());
Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes);
}
image.ComparePixelBufferTo(targetBuffer);
}
}
}
}

40
tests/ImageSharp.Tests/Common/SimdUtilsTests.cs

@ -8,6 +8,8 @@ namespace SixLabors.ImageSharp.Tests.Common
using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Tuples;
using Xunit.Abstractions;
using Xunit.Sdk;
@ -109,6 +111,16 @@ namespace SixLabors.ImageSharp.Tests.Common
AssertEvenRoundIsCorrect(r, v);
}
private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null)
{
if (!SimdUtils.IsAvx2CompatibleArchitecture)
{
this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName);
return true;
}
return false;
}
[Theory]
[InlineData(1, 0)]
[InlineData(1, 8)]
@ -116,6 +128,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByte_WithRoundedData(int seed, int count)
{
if (this.SkipOnNonAvx2())
{
return;
}
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, 0, 256);
float[] normalized = orig.Select(f => f / 255f).ToArray();
@ -135,6 +152,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByte_WithNonRoundedData(int seed, int count)
{
if (this.SkipOnNonAvx2())
{
return;
}
float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f);
byte[] dest = new byte[count];
@ -155,6 +177,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count)
{
if (this.SkipOnNonAvx2())
{
return;
}
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444);
float[] normalized = orig.Select(f => f / 255f).ToArray();
@ -185,6 +212,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[Fact]
private void BulkConvertNormalizedFloatToByte_Step()
{
if (this.SkipOnNonAvx2())
{
return;
}
float[] source = {0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f};
byte[] expected = source.Select(f => (byte)Math.Round(f)).ToArray();
@ -213,15 +245,15 @@ namespace SixLabors.ImageSharp.Tests.Common
x = (x * scale) + magick;
SimdUtils.Octet.OfUInt32 ii = default(SimdUtils.Octet.OfUInt32);
Tuple8.OfUInt32 ii = default(Tuple8.OfUInt32);
ref Vector<float> iiRef = ref Unsafe.As<SimdUtils.Octet.OfUInt32, Vector<float>>(ref ii);
ref Vector<float> iiRef = ref Unsafe.As<Tuple8.OfUInt32, Vector<float>>(ref ii);
iiRef = x;
//SimdUtils.Octet.OfUInt32 ii = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x);
//Tuple8.OfUInt32 ii = Unsafe.As<Vector<float>, Tuple8.OfUInt32>(ref x);
ref SimdUtils.Octet.OfByte d = ref dest.NonPortableCast<byte, SimdUtils.Octet.OfByte>()[0];
ref Tuple8.OfByte d = ref dest.NonPortableCast<byte, Tuple8.OfByte>()[0];
d.LoadFrom(ref ii);
this.Output.WriteLine(ii.ToString());

108
tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs

@ -0,0 +1,108 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Common
{
public class StreamExtensionsTests
{
[Theory]
[InlineData(0)]
[InlineData(-1)]
public void Skip_CountZeroOrLower_PositionNotChanged(int count)
{
using (var memStream = new MemoryStream(5))
{
memStream.Position = 4;
memStream.Skip(count);
Assert.Equal(4, memStream.Position);
}
}
[Fact]
public void Skip_SeekableStream_SeekIsCalled()
{
using (var seekableStream = new SeekableStream(4))
{
seekableStream.Skip(4);
Assert.Equal(4, seekableStream.Offset);
Assert.Equal(SeekOrigin.Current, seekableStream.Loc);
}
}
[Fact]
public void Skip_NonSeekableStream_BytesAreRead()
{
using (var nonSeekableStream = new NonSeekableStream())
{
nonSeekableStream.Skip(5);
Assert.Equal(3, nonSeekableStream.Counts.Count);
Assert.Equal(5, nonSeekableStream.Counts[0]);
Assert.Equal(3, nonSeekableStream.Counts[1]);
Assert.Equal(1, nonSeekableStream.Counts[2]);
}
}
[Fact]
public void Skip_EofStream_NoExceptionIsThrown()
{
using (var eofStream = new EofStream(7))
{
eofStream.Skip(7);
Assert.Equal(0, eofStream.Position);
}
}
private class SeekableStream : MemoryStream
{
public long Offset;
public SeekOrigin Loc;
public SeekableStream(int capacity) : base(capacity) { }
public override long Seek(long offset, SeekOrigin loc)
{
this.Offset = offset;
this.Loc = loc;
return base.Seek(offset, loc);
}
}
private class NonSeekableStream : MemoryStream
{
public override bool CanSeek => false;
public List<int> Counts = new List<int>();
public NonSeekableStream() : base(4) { }
public override int Read(byte[] buffer, int offset, int count)
{
this.Counts.Add(count);
return Math.Min(2, count);
}
}
private class EofStream : MemoryStream
{
public override bool CanSeek => false;
public EofStream(int capacity) : base(capacity) { }
public override int Read(byte[] buffer, int offset, int count)
{
return 0;
}
}
}
}

189
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -32,32 +32,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
}
[Fact]
public void Indexer()
private bool SkipOnNonAvx2Runner()
{
float sum = 0;
this.Measure(
Times,
() =>
{
var block = new Block8x8F();
for (int i = 0; i < Block8x8F.Size; i++)
{
block[i] = i;
}
sum = 0;
for (int i = 0; i < Block8x8F.Size; i++)
{
sum += block[i];
}
});
Assert.Equal(sum, 64f * 63f * 0.5f);
if (!SimdUtils.IsAvx2CompatibleArchitecture)
{
this.Output.WriteLine("AVX2 not supported, skipping!");
return true;
}
return false;
}
[Fact]
public unsafe void Indexer_GetScalarAt_SetScalarAt()
public void Indexer()
{
float sum = 0;
this.Measure(
@ -68,18 +54,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
for (int i = 0; i < Block8x8F.Size; i++)
{
Block8x8F.SetScalarAt(&block, i, i);
block[i] = i;
}
sum = 0;
for (int i = 0; i < Block8x8F.Size; i++)
{
sum += Block8x8F.GetScalarAt(&block, i);
sum += block[i];
}
});
Assert.Equal(sum, 64f * 63f * 0.5f);
}
[Fact]
public void Indexer_ReferenceBenchmarkWithArray()
{
@ -222,33 +208,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
}
[Fact]
public unsafe void CopyColorsTo()
{
float[] data = Create8x8FloatData();
var block = new Block8x8F();
block.LoadFrom(data);
block.MultiplyAllInplace(5);
int stride = 256;
int height = 42;
int offset = height * 10 + 20;
byte[] colorsExpected = new byte[stride * height];
byte[] colorsActual = new byte[stride * height];
var temp = new Block8x8F();
ReferenceImplementations.CopyColorsTo(ref block, new Span<byte>(colorsExpected, offset), stride);
block.CopyColorsTo(new Span<byte>(colorsActual, offset), stride, &temp);
// Output.WriteLine("******* EXPECTED: *********");
// PrintLinearData(colorsExpected);
// Output.WriteLine("******** ACTUAL: **********");
Assert.Equal(colorsExpected, colorsActual);
}
private static float[] Create8x8ColorCropTestData()
{
float[] result = new float[64];
@ -263,30 +222,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return result;
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void NormalizeColors(bool inplace)
[Fact]
public void NormalizeColors()
{
var block = default(Block8x8F);
float[] input = Create8x8ColorCropTestData();
block.LoadFrom(input);
this.Output.WriteLine("Input:");
this.PrintLinearData(input);
var dest = default(Block8x8F);
if (inplace)
{
dest = block;
dest.NormalizeColorsInplace();
}
else
{
block.NormalizeColorsInto(ref dest);
}
Block8x8F dest = block;
dest.NormalizeColorsInplace();
float[] array = new float[64];
dest.CopyTo(array);
this.Output.WriteLine("Result:");
@ -297,11 +244,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void NormalizeColorsAndRoundAvx2(int seed)
{
if (this.SkipOnNonAvx2Runner())
{
return;
}
Block8x8F source = CreateRandomFloatBlock(-200, 200, seed);
Block8x8F expected = source;
expected.NormalizeColorsInplace();
expected.RoundInplace();
Block8x8F actual = source;
actual.NormalizeColorsAndRoundInplaceAvx2();
this.Output.WriteLine(expected.ToString());
this.Output.WriteLine(actual.ToString());
this.CompareBlocks(expected, actual, 0);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public unsafe void UnzigDivRound(int seed)
public unsafe void Quantize(int seed)
{
var block = new Block8x8F();
block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed));
@ -309,14 +280,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var qt = new Block8x8F();
qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed));
var unzig = UnzigData.Create();
var unzig = ZigZag.CreateUnzigTable();
int* expectedResults = stackalloc int[Block8x8F.Size];
ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data);
ReferenceImplementations.QuantizeRational(&block, expectedResults, &qt, unzig.Data);
var actualResults = default(Block8x8F);
Block8x8F.UnzigDivRound(&block, &actualResults, &qt, unzig.Data);
Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data);
for (int i = 0; i < Block8x8F.Size; i++)
{
@ -352,7 +323,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void RoundInplace(int seed)
public void RoundInplaceSlow(int seed)
{
Block8x8F s = CreateRandomFloatBlock(-500, 500, seed);
@ -370,5 +341,77 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual);
}
}
[Fact]
public void MultiplyInplace_ByOtherBlock()
{
Block8x8F original = CreateRandomFloatBlock(-500, 500, 42);
Block8x8F m = CreateRandomFloatBlock(-500, 500, 42);
Block8x8F actual = original;
actual.MultiplyInplace(ref m);
for (int i = 0; i < Block8x8F.Size; i++)
{
Assert.Equal(original[i]*m[i], actual[i]);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public unsafe void DequantizeBlock(int seed)
{
Block8x8F original = CreateRandomFloatBlock(-500, 500, seed);
Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42);
var unzig = ZigZag.CreateUnzigTable();
Block8x8F expected = original;
Block8x8F actual = original;
ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data);
Block8x8F.DequantizeBlock(&actual, &qt, unzig.Data);
this.CompareBlocks(expected, actual, 0);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public unsafe void ZigZag_CreateDequantizationTable_MultiplicationShouldQuantize(int seed)
{
Block8x8F original = CreateRandomFloatBlock(-500, 500, seed);
Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42);
var unzig = ZigZag.CreateUnzigTable();
Block8x8F zigQt = ZigZag.CreateDequantizationTable(ref qt);
Block8x8F expected = original;
Block8x8F actual = original;
ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data);
actual.MultiplyInplace(ref zigQt);
this.CompareBlocks(expected, actual, 0);
}
[Fact]
public void MultiplyInplace_ByScalar()
{
Block8x8F original = CreateRandomFloatBlock(-500, 500);
Block8x8F actual = original;
actual.MultiplyInplace(42f);
for (int i = 0; i < 64; i++)
{
Assert.Equal(original[i]*42f, actual[i]);
}
}
}
}

2
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public class JpegColorConverterTests
{
private const float Precision = 1/255f;
private const float Precision = 0.1f / 255;
public static readonly TheoryData<int, int, int> CommonConversionData =
new TheoryData<int, int, int>

126
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -9,6 +9,7 @@ using System;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -24,6 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using Xunit;
using Xunit.Abstractions;
// TODO: Scatter test cases into multiple test classes
public class JpegDecoderTests
{
public static string[] BaselineTestJpegs =
@ -50,16 +52,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159,
};
private static readonly Dictionary<string, float> CustomToleranceValues = new Dictionary<string, float>
{
// Baseline:
[TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100,
[TestImages.Jpeg.Baseline.Bad.ExifUndefType] = 0.011f / 100,
[TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100,
[TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100,
// Progressive:
[TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100,
[TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100,
[TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100,
[TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100,
[TestImages.Jpeg.Progressive.Fb] = 0.16f / 100,
[TestImages.Jpeg.Progressive.Progress] = 0.31f / 100,
};
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector;
// TODO: We should make this comparer less tolerant ...
private static readonly ImageComparer VeryTolerantJpegComparer =
ImageComparer.Tolerant(0.005f, perPixelManhattanThreshold: 4);
private const float BaselineTolerance_Orig = 0.001f / 100;
private const float BaselineTolerance_PdfJs = 0.005f;
// BUG: PDF.js output is wrong on spectral level!
private static readonly ImageComparer PdfJsProgressiveComparer =
ImageComparer.Tolerant(0.015f, perPixelManhattanThreshold: 4);
private const float ProgressiveTolerance_Orig = 0.2f / 100;
private const float ProgressiveTolerance_PdfJs = 1.5f / 100; // PDF.js Progressive output is wrong on spectral level!
private ImageComparer GetImageComparerForOrigDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string file = provider.SourceFileOrDescription;
if (!CustomToleranceValues.TryGetValue(file, out float tolerance))
{
tolerance = file.ToLower().Contains("baseline") ? BaselineTolerance_Orig : ProgressiveTolerance_Orig;
}
return ImageComparer.Tolerant(tolerance);
}
public JpegDecoderTests(ITestOutputHelper output)
{
@ -71,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static IImageDecoder OrigJpegDecoder => new OrigJpegDecoder();
private static IImageDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder();
[Fact]
public void ParseStream_BasicPropertiesAreCorrect1_PdfJs()
{
@ -84,50 +113,56 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31);
}
}
public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg";
[Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)]
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, bool useOldDecoder)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder;
using (Image<TPixel> image = provider.GetImage(decoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(provider, ImageComparer.Tolerant(BaselineTolerance_PdfJs), appendPixelTypeToFileName: false);
}
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)]
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, bool useOldDecoder)
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder;
using (Image<TPixel> image = provider.GetImage(decoder))
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
provider,
this.GetImageComparerForOrigDecoder(provider),
appendPixelTypeToFileName: false);
}
}
[Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
public void DecodeBaselineJpeg_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
provider,
ImageComparer.Tolerant(BaselineTolerance_PdfJs),
appendPixelTypeToFileName: false);
}
}
@ -144,37 +179,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)]
public void DecodeProgressiveJpeg_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
public void DecodeProgressiveJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeProgressiveJpegOutputName;
image.CompareToReferenceOutput(provider, PdfJsProgressiveComparer, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
provider,
this.GetImageComparerForOrigDecoder(provider),
appendPixelTypeToFileName: false);
}
}
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)]
public void DecodeProgressiveJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
public void DecodeProgressiveJpeg_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeProgressiveJpegOutputName;
image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
provider,
ImageComparer.Tolerant(ProgressiveTolerance_PdfJs),
appendPixelTypeToFileName: false);
}
}
private float GetDifferenceInPercents<TPixel>(Image<TPixel> image, TestImageProvider<TPixel> provider)
private string GetDifferenceInPercentageString<TPixel>(Image<TPixel> image, TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
var reportingComparer = ImageComparer.Tolerant(0, 0);
ImageSimilarityReport report = image.GetReferenceOutputSimilarityReports(
provider,
reportingComparer,
@ -183,10 +224,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (report != null && report.TotalNormalizedDifference.HasValue)
{
return report.TotalNormalizedDifference.Value * 100;
return report.DifferencePercentageString;
}
return 0;
return "0%";
}
private void CompareJpegDecodersImpl<TPixel>(TestImageProvider<TPixel> provider, string testName)
@ -202,14 +243,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image<TPixel> image = provider.GetImage(OrigJpegDecoder))
{
double d = this.GetDifferenceInPercents(image, provider);
this.Output.WriteLine($"Difference using ORIGINAL decoder: {d:0.0000}%");
string d = this.GetDifferenceInPercentageString(image, provider);
this.Output.WriteLine($"Difference using ORIGINAL decoder: {d}");
}
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
{
double d = this.GetDifferenceInPercents(image, provider);
this.Output.WriteLine($"Difference using PDFJS decoder: {d:0.0000}%");
string d = this.GetDifferenceInPercentageString(image, provider);
this.Output.WriteLine($"Difference using PDFJS decoder: {d}");
}
}
@ -228,7 +270,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
this.CompareJpegDecodersImpl(provider, DecodeProgressiveJpegOutputName);
}
[Theory]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 75)]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 100)]
@ -256,7 +298,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var mirror = Image.Load<TPixel>(data, OrigJpegDecoder);
mirror.DebugSave(provider, $"_{subsample}_Q{quality}");
}
[Fact]
public void Decoder_Reads_Correct_Resolution_From_Jfif()
{
@ -314,7 +356,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// into "\tests\Images\ActualOutput\JpegDecoderTests\"
//[Theory]
//[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")]
public void ValidateProgressivePdfJsOutput<TPixel>(TestImageProvider<TPixel> provider,
public void ValidateProgressivePdfJsOutput<TPixel>(TestImageProvider<TPixel> provider,
string pdfJsOriginalResultImage)
where TPixel : struct, IPixel<TPixel>
{
@ -335,7 +377,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult);
ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult);
this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}");
this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}");
}

10
tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs

@ -34,15 +34,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Jpeg444,
};
[Theory] // Benchmark, enable manually
[MemberData(nameof(DecodeJpegData))]
//[Theory] // Benchmark, enable manually
//[MemberData(nameof(DecodeJpegData))]
public void DecodeJpeg_Original(string fileName)
{
this.DecodeJpegBenchmarkImpl(fileName, new OrigJpegDecoder());
}
[Theory] // Benchmark, enable manually
[MemberData(nameof(DecodeJpegData))]
// [Theory] // Benchmark, enable manually
// [MemberData(nameof(DecodeJpegData))]
public void DecodeJpeg_PdfJs(string fileName)
{
this.DecodeJpegBenchmarkImpl(fileName, new PdfJsJpegDecoder());
@ -106,4 +106,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
}
}

21
tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs

@ -19,6 +19,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
/// </summary>
internal static partial class ReferenceImplementations
{
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
for (int qtIndex = 0; qtIndex < Block8x8F.Size; qtIndex++)
{
int i = unzigPtr[qtIndex];
float* unzigPos = b + i;
float val = *unzigPos;
val *= qtp[qtIndex];
*unzigPos = val;
}
}
/// <summary>
/// Transpose 8x8 block stored linearly in a <see cref="Span{T}"/> (inplace)
/// </summary>
@ -93,14 +108,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
}
/// <summary>
/// Reference implementation to test <see cref="Block8x8F.UnzigDivRound"/>.
/// Reference implementation to test <see cref="Block8x8F.Quantize"/>.
/// Rounding is done used an integer-based algorithm defined in <see cref="RationalRound(int,int)"/>.
/// </summary>
/// <param name="src">The input block</param>
/// <param name="dest">The destination block of integers</param>
/// <param name="qt">The quantization table</param>
/// <param name="unzigPtr">Pointer to <see cref="UnzigData.Data"/> </param>
public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr)
/// <param name="unzigPtr">Pointer to <see cref="ZigZag.Data"/> </param>
public static unsafe void QuantizeRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr)
{
float* s = (float*)src;
float* q = (float*)qt;

80
tests/ImageSharp.Tests/Image/ImageSaveTests.cs

@ -9,9 +9,12 @@ using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
using Moq;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests
{
using System.Runtime.CompilerServices;
/// <summary>
/// Tests the <see cref="Image"/> class.
/// </summary>
@ -47,76 +50,39 @@ namespace SixLabors.ImageSharp.Tests
this.Image = new Image<Rgba32>(config, 1, 1);
}
[Fact]
public void SavePixelData_Rgba32()
[Theory]
[WithTestPatternImages(13, 19, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void SavePixelData_ToPixelStructArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (var img = new Image<Rgba32>(2, 2))
using (Image<TPixel> image = provider.GetImage())
{
img[0, 0] = Rgba32.White;
img[1, 0] = Rgba32.Black;
TPixel[] buffer = new TPixel[image.Width*image.Height];
image.SavePixelData(buffer);
img[0, 1] = Rgba32.Red;
img[1, 1] = Rgba32.Blue;
var buffer = new byte[2 * 2 * 4]; // width * height * bytes per pixel
img.SavePixelData(buffer);
Assert.Equal(255, buffer[0]); // 0, 0, R
Assert.Equal(255, buffer[1]); // 0, 0, G
Assert.Equal(255, buffer[2]); // 0, 0, B
Assert.Equal(255, buffer[3]); // 0, 0, A
Assert.Equal(0, buffer[4]); // 1, 0, R
Assert.Equal(0, buffer[5]); // 1, 0, G
Assert.Equal(0, buffer[6]); // 1, 0, B
Assert.Equal(255, buffer[7]); // 1, 0, A
Assert.Equal(255, buffer[8]); // 0, 1, R
Assert.Equal(0, buffer[9]); // 0, 1, G
Assert.Equal(0, buffer[10]); // 0, 1, B
Assert.Equal(255, buffer[11]); // 0, 1, A
Assert.Equal(0, buffer[12]); // 1, 1, R
Assert.Equal(0, buffer[13]); // 1, 1, G
Assert.Equal(255, buffer[14]); // 1, 1, B
Assert.Equal(255, buffer[15]); // 1, 1, A
image.ComparePixelBufferTo(buffer);
// TODO: We need a separate test-case somewhere ensuring that image pixels are stored in row-major order!
}
}
[Fact]
public void SavePixelData_Bgr24()
[Theory]
[WithTestPatternImages(19, 13, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void SavePixelData_ToByteArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (var img = new Image<Bgr24>(2, 2))
using (Image<TPixel> image = provider.GetImage())
{
img[0, 0] = NamedColors<Bgr24>.White;
img[1, 0] = NamedColors<Bgr24>.Black;
img[0, 1] = NamedColors<Bgr24>.Red;
img[1, 1] = NamedColors<Bgr24>.Blue;
var buffer = new byte[2 * 2 * 3]; // width * height * bytes per pixel
img.SavePixelData(buffer);
byte[] buffer = new byte[image.Width*image.Height*Unsafe.SizeOf<TPixel>()];
Assert.Equal(255, buffer[0]); // 0, 0, B
Assert.Equal(255, buffer[1]); // 0, 0, G
Assert.Equal(255, buffer[2]); // 0, 0, R
image.SavePixelData(buffer);
Assert.Equal(0, buffer[3]); // 1, 0, B
Assert.Equal(0, buffer[4]); // 1, 0, G
Assert.Equal(0, buffer[5]); // 1, 0, R
Assert.Equal(0, buffer[6]); // 0, 1, B
Assert.Equal(0, buffer[7]); // 0, 1, G
Assert.Equal(255, buffer[8]); // 0, 1, R
Assert.Equal(255, buffer[9]); // 1, 1, B
Assert.Equal(0, buffer[10]); // 1, 1, G
Assert.Equal(0, buffer[11]); // 1, 1, R
image.ComparePixelBufferTo(buffer.AsSpan().NonPortableCast<byte, TPixel>());
}
}
[Fact]
public void SavePixelData_Rgba32_Buffer_must_be_bigger()
public void SavePixelData_Rgba32_WhenBufferIsTooSmall_Throws()
{
using (var img = new Image<Rgba32>(2, 2))
{

2
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
SizeF newSize = image.Size() * ratio;
image.Mutate(x => x.Resize((Size)newSize, sampler, false));
string details = $"{name}-{ratio}";
string details = $"{name}-{ratio.ToString(System.Globalization.CultureInfo.InvariantCulture)}";
image.DebugSave(provider, details);
image.CompareToReferenceOutput(provider, details);

2
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests
{
float d = x - y;
return d > -this.Eps && d < this.Eps;
return d >= -this.Eps && d <= this.Eps;
}
public int GetHashCode(float obj)

21
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs

@ -26,9 +26,24 @@
// TODO: This should not be a nullable value!
public float? TotalNormalizedDifference { get; }
public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue
? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"
: "?";
public string DifferencePercentageString
{
get
{
if (!this.TotalNormalizedDifference.HasValue)
{
return "?";
}
else if (this.TotalNormalizedDifference == 0)
{
return "0%";
}
else
{
return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%";
}
}
}
public PixelDifference[] Differences { get; }

13
tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

@ -105,15 +105,20 @@ namespace SixLabors.ImageSharp.Tests
private static readonly ConcurrentDictionary<Key, Image<TPixel>> cache = new ConcurrentDictionary<Key, Image<TPixel>>();
public FileProvider(string filePath)
// Needed for deserialization!
// ReSharper disable once UnusedMember.Local
public FileProvider()
{
this.FilePath = filePath;
}
public FileProvider()
public FileProvider(string filePath)
{
this.FilePath = filePath;
}
/// <summary>
/// Gets the file path relative to the "~/tests/images" folder
/// </summary>
public string FilePath { get; private set; }
public override string SourceFileOrDescription => this.FilePath;

10
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -89,6 +89,8 @@ namespace SixLabors.ImageSharp.Tests
return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}";
}
private static string Inv(FormattableString formattable) => System.FormattableString.Invariant(formattable);
/// <summary>
/// Gets the recommended file name for the output of the test
/// </summary>
@ -111,13 +113,17 @@ namespace SixLabors.ImageSharp.Tests
TypeInfo info = type.GetTypeInfo();
if (info.IsPrimitive || info.IsEnum || type == typeof(decimal))
{
detailsString = testOutputDetails.ToString();
detailsString = Inv($"{testOutputDetails}");
}
else
{
IEnumerable<PropertyInfo> properties = testOutputDetails.GetType().GetRuntimeProperties();
detailsString = String.Join("_", properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)).Select(x => $"{x.Key}-{x.Value}"));
detailsString = string.Join(
"_",
properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails))
.Select(x => Inv($"{x.Key}-{x.Value}"))
);
}
}
return this.GetTestOutputFileNameImpl(extension, detailsString, appendPixelTypeToFileName);

24
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Tests
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using Xunit;
public static class TestImageExtensions
{
/// <summary>
@ -81,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests
grayscale,
appendPixelTypeToFileName);
}
/// <summary>
/// Compares the image against the expected Reference output, throws an exception if the images are not similar enough.
/// The output file should be named identically to the output produced by <see cref="DebugSave{TPixel}(Image{TPixel}, ITestImageProvider, object, string, bool)"/>.
@ -153,6 +155,24 @@ namespace SixLabors.ImageSharp.Tests
}
}
public static Image<TPixel> ComparePixelBufferTo<TPixel>(
this Image<TPixel> image,
Span<TPixel> expectedPixels)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> actual = image.GetPixelSpan();
Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!");
for (int i = 0; i < expectedPixels.Length; i++)
{
Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!" );
}
return image;
}
public static Image<TPixel> CompareToOriginal<TPixel>(
this Image<TPixel> image,
ITestImageProvider provider)
@ -160,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests
{
return CompareToOriginal(image, provider, ImageComparer.Tolerant());
}
public static Image<TPixel> CompareToOriginal<TPixel>(
this Image<TPixel> image,
ITestImageProvider provider,

Loading…
Cancel
Save