Browse Source

Merge branch 'refs/heads/pr/58'

af/merge-core
James Jackson-South 9 years ago
parent
commit
5e74acfaba
  1. 2
      ImageSharp.sln.DotSettings
  2. 272
      src/ImageSharp/Formats/Jpg/Components/Block.cs
  3. 11
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs
  4. 11
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt
  5. 413
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
  6. 18
      src/ImageSharp/Formats/Jpg/Components/BlockQuad.cs
  7. 340
      src/ImageSharp/Formats/Jpg/Components/DCT.cs
  8. 6
      src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs
  9. 2
      src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs
  10. 2
      src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs
  11. 2
      src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs
  12. 6
      src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs
  13. 2
      src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
  14. 36
      src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs
  15. 73
      src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs
  16. 136
      src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs
  17. 22
      src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs
  18. 162
      src/ImageSharp/Formats/Jpg/Components/FDCT.cs
  19. 169
      src/ImageSharp/Formats/Jpg/Components/IDCT.cs
  20. 81
      src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs
  21. 127
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  22. 965
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  23. 48
      src/ImageSharp/Formats/Jpg/UnzigData.cs
  24. 123
      src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs
  25. 2
      src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs
  26. 164
      src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs
  27. 2
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  28. 144
      src/ImageSharp/Image/PixelArea{TColor}.cs
  29. 3
      src/ImageSharp/ImageSharp.xproj
  30. 2
      src/ImageSharp/Numerics/RectangleF.cs
  31. 20
      src/ImageSharp/PixelAccessor.cs
  32. 1
      src/ImageSharp/Properties/AssemblyInfo.cs
  33. 96
      tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs
  34. 50
      tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs
  35. 185
      tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
  36. 341
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  37. 166
      tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
  38. 542
      tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs
  39. 127
      tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs
  40. 107
      tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs
  41. 7
      tests/ImageSharp.Tests/TestFile.cs
  42. 2
      tests/ImageSharp.Tests/TestImages.cs
  43. 5
      tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs
  44. 6
      tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs
  45. 20
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
  46. 8
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  47. 1
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

2
ImageSharp.sln.DotSettings

@ -340,6 +340,7 @@
&lt;/Patterns&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/QualifiedUsingAtNestedScope/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DC/@EntryIndexedValue">DC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FDCT/@EntryIndexedValue">FDCT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IDCT/@EntryIndexedValue">IDCT</s:String>
@ -348,6 +349,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RLE/@EntryIndexedValue">RLE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XY/@EntryIndexedValue">XY</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XYZ/@EntryIndexedValue">XYZ</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/EventHandlerPatternLong/@EntryValue">$object$_On$event$</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>

272
src/ImageSharp/Formats/Jpg/Components/Block.cs

@ -1,272 +0,0 @@
// <copyright file="Block.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an 8x8 block of coefficients to transform and encode.
/// </summary>
internal struct Block : IDisposable
{
/// <summary>
/// Gets the size of the block.
/// </summary>
public const int BlockSize = 64;
/// <summary>
/// Gets the array of block data.
/// </summary>
public int[] Data;
/// <summary>
/// A pool of reusable buffers.
/// </summary>
private static readonly ArrayPool<int> ArrayPool = ArrayPool<int>.Create(BlockSize, 50);
/// <summary>
/// Gets a value indicating whether the block is initialized
/// </summary>
public bool IsInitialized => this.Data != null;
/// <summary>
/// Gets the pixel data at the given block index.
/// </summary>
/// <param name="index">The index of the data to return.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public int this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Data[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.Data[index] = value;
}
}
/// <summary>
/// Creates a new block
/// </summary>
/// <returns>The <see cref="Block"/></returns>
public static Block Create()
{
Block block = default(Block);
block.Init();
return block;
}
/// <summary>
/// Returns an array of blocks of the given length.
/// </summary>
/// <param name="count">The number to create.</param>
/// <returns>The <see cref="T:Block[]"/></returns>
public static Block[] CreateArray(int count)
{
Block[] result = new Block[count];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
/// <summary>
/// Disposes of the collection of blocks
/// </summary>
/// <param name="blocks">The blocks.</param>
public static void DisposeAll(Block[] blocks)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
}
/// <summary>
/// Initializes the new block.
/// </summary>
public void Init()
{
this.Data = ArrayPool.Rent(BlockSize);
}
/// <inheritdoc />
public void Dispose()
{
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
if (this.Data != null)
{
ArrayPool.Return(this.Data, true);
this.Data = null;
}
}
/// <summary>
/// Clears the block data
/// </summary>
public void Clear()
{
for (int i = 0; i < this.Data.Length; i++)
{
this.Data[i] = 0;
}
}
/// <summary>
/// Clones the current block
/// </summary>
/// <returns>The <see cref="Block"/></returns>
public Block Clone()
{
Block clone = Create();
Array.Copy(this.Data, clone.Data, BlockSize);
return clone;
}
}
/// <summary>
/// TODO: Should be removed, when JpegEncoderCore is refactored to use Block8x8F
/// Temporal class to make refactoring easier.
/// 1. Refactor Block -> BlockF
/// 2. Test
/// 3. Refactor BlockF -> Block8x8F
/// </summary>
internal struct BlockF : IDisposable
{
/// <summary>
/// Size of the block.
/// </summary>
public const int BlockSize = 64;
/// <summary>
/// The array of block data.
/// </summary>
public float[] Data;
/// <summary>
/// A pool of reusable buffers.
/// </summary>
private static readonly ArrayPool<float> ArrayPool = ArrayPool<float>.Create(BlockSize, 50);
/// <summary>
/// Gets a value indicating whether the block is initialized
/// </summary>
public bool IsInitialized => this.Data != null;
/// <summary>
/// Gets the pixel data at the given block index.
/// </summary>
/// <param name="index">The index of the data to return.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public float this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Data[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.Data[index] = value;
}
}
/// <summary>
/// Creates a new block
/// </summary>
/// <returns>The <see cref="BlockF"/></returns>
public static BlockF Create()
{
var block = default(BlockF);
block.Init();
return block;
}
/// <summary>
/// Returns an array of blocks of the given length.
/// </summary>
/// <param name="count">The number to create.</param>
/// <returns>The <see cref="T:BlockF[]"/></returns>
public static BlockF[] CreateArray(int count)
{
BlockF[] result = new BlockF[count];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
/// <summary>
/// Disposes of the collection of blocks
/// </summary>
/// <param name="blocks">The blocks.</param>
public static void DisposeAll(BlockF[] blocks)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
}
/// <summary>
/// Clears the block data
/// </summary>
public void Clear()
{
for (int i = 0; i < this.Data.Length; i++)
{
this.Data[i] = 0;
}
}
/// <summary>
/// Clones the current block
/// </summary>
/// <returns>The <see cref="Block"/></returns>
public BlockF Clone()
{
BlockF clone = Create();
Array.Copy(this.Data, clone.Data, BlockSize);
return clone;
}
/// <summary>
/// Initializes the new block.
/// </summary>
public void Init()
{
// this.Data = new int[BlockSize];
this.Data = ArrayPool.Rent(BlockSize);
}
/// <inheritdoc />
public void Dispose()
{
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
if (this.Data != null)
{
ArrayPool.Return(this.Data, true);
this.Data = null;
}
}
}
}

11
src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs

@ -1,11 +1,16 @@
// <auto-generated />
// <copyright file="Block8x8F.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
// <auto-generated />
#pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
internal partial struct Block8x8F
{

11
src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt

@ -1,11 +1,16 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
// <copyright file="Block8x8F.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
// <auto-generated />
#pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -14,7 +19,7 @@ using System.Runtime.CompilerServices;
char[] coordz = {'X', 'Y', 'Z', 'W'};
#>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
internal partial struct Block8x8F
{

413
src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Numerics;
@ -15,6 +15,11 @@ namespace ImageSharp.Formats
/// </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>
@ -51,25 +56,8 @@ namespace ImageSharp.Formats
public Vector4 V7R;
#pragma warning restore SA1600 // ElementsMustBeDocumented
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private static readonly Vector4 C_1_175876 = new Vector4(1.175876f);
private static readonly Vector4 C_1_961571 = new Vector4(-1.961571f);
private static readonly Vector4 C_0_390181 = new Vector4(-0.390181f);
private static readonly Vector4 C_0_899976 = new Vector4(-0.899976f);
private static readonly Vector4 C_2_562915 = new Vector4(-2.562915f);
private static readonly Vector4 C_0_298631 = new Vector4(0.298631f);
private static readonly Vector4 C_2_053120 = new Vector4(2.053120f);
private static readonly Vector4 C_3_072711 = new Vector4(3.072711f);
private static readonly Vector4 C_1_501321 = new Vector4(1.501321f);
private static readonly Vector4 C_0_541196 = new Vector4(0.541196f);
private static readonly Vector4 C_1_847759 = new Vector4(-1.847759f);
private static readonly Vector4 C_0_765367 = new Vector4(0.765367f);
private static readonly Vector4 C_0_125 = new Vector4(0.1250f);
#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
/// <summary>
/// Index into the block
/// Get/Set scalar elements at a given index
/// </summary>
/// <param name="idx">The index</param>
/// <returns>The float value at the specified index</returns>
@ -97,25 +85,39 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Load raw 32bit floating point data from source
/// Pointer-based "Indexer" (getter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="source">Source</param>
/// <param name="idx">Index</param>
/// <returns>The scaleVec value at the specified index</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void LoadFrom(Block8x8F* blockPtr, MutableSpan<float> source)
public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
{
Marshal.Copy(source.Data, source.Offset, (IntPtr)blockPtr, ScalarCount);
float* fp = (float*)blockPtr;
return fp[idx];
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// Pointer-based "Indexer" (setter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="dest">Destination</param>
/// <param name="idx">Index</param>
/// <param name="value">Value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan<float> dest)
public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
{
Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount);
float* fp = (float*)blockPtr;
fp[idx] = value;
}
/// <summary>
/// Fill the block with defaults (zeroes)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
// The cheapest way to do this in C#:
this = default(Block8x8F);
}
/// <summary>
@ -132,118 +134,82 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// Load raw 32bit floating point data from source
/// </summary>
/// <param name="dest">Destination</param>
/// <param name="blockPtr">Block pointer</param>
/// <param name="source">Source</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyTo(MutableSpan<float> dest)
public static unsafe void LoadFrom(Block8x8F* blockPtr, MutableSpan<float> source)
{
fixed (void* ptr = &this.V0L)
{
Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount);
}
Marshal.Copy(source.Data, source.Offset, (IntPtr)blockPtr, ScalarCount);
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// Load raw 32bit floating point data from source
/// </summary>
/// <param name="dest">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyTo(float[] dest)
/// <param name="source">Source</param>
public unsafe void LoadFrom(MutableSpan<int> source)
{
fixed (void* ptr = &this.V0L)
fixed (Vector4* ptr = &this.V0L)
{
Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount);
float* fp = (float*)ptr;
for (int i = 0; i < ScalarCount; i++)
{
fp[i] = source[i];
}
}
}
/// <summary>
/// Multiply in place
/// Copy raw 32bit floating point data to dest
/// </summary>
/// <param name="scalar">Scalar to multiply by</param>
/// <param name="dest">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyAllInplace(Vector4 scalar)
public unsafe void CopyTo(MutableSpan<float> dest)
{
this.V0L *= scalar;
this.V0R *= scalar;
this.V1L *= scalar;
this.V1R *= scalar;
this.V2L *= scalar;
this.V2R *= scalar;
this.V3L *= scalar;
this.V3R *= scalar;
this.V4L *= scalar;
this.V4R *= scalar;
this.V5L *= scalar;
this.V5R *= scalar;
this.V6L *= scalar;
this.V6R *= scalar;
this.V7L *= scalar;
this.V7R *= scalar;
fixed (void* ptr = &this.V0L)
{
Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount);
}
}
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
/// Convert salars to byte-s and copy to dest
/// </summary>
/// <param name="blockPtr">Pointer to block</param>
/// <param name="dest">Destination</param>
/// <param name="tempBlockPtr">Temporary block provided by the caller</param>
public void TransformIDCTInto(ref Block8x8F dest, ref Block8x8F tempBlockPtr)
{
this.TransposeInto(ref tempBlockPtr);
tempBlockPtr.IDCT8x4_LeftPart(ref dest);
tempBlockPtr.IDCT8x4_RightPart(ref dest);
dest.TransposeInto(ref tempBlockPtr);
tempBlockPtr.IDCT8x4_LeftPart(ref dest);
tempBlockPtr.IDCT8x4_RightPart(ref dest);
dest.MultiplyAllInplace(C_0_125);
}
/// <summary>
/// Pointer-based "Indexer" (getter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <returns>The scalar value at the specified index</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan<byte> dest)
{
float* fp = (float*)blockPtr;
return fp[idx];
float* fPtr = (float*)blockPtr;
for (int i = 0; i < ScalarCount; i++)
{
dest[i] = (byte)*fPtr;
fPtr++;
}
}
/// <summary>
/// Pointer-based "Indexer" (setter part)
/// Copy raw 32bit floating point data to dest
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <param name="value">Value</param>
/// <param name="dest">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan<float> dest)
{
float* fp = (float*)blockPtr;
fp[idx] = value;
Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount);
}
/// <summary>
/// Un-zig
/// Copy raw 32bit floating point data to dest
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="qtPtr">Qt pointer</param>
/// <param name="unzigPtr">Unzig pointer</param>
/// <param name="dest">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void UnZig(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
public unsafe void CopyTo(float[] dest)
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
for (int zig = 0; zig < BlockF.BlockSize; zig++)
fixed (void* ptr = &this.V0L)
{
float* unzigPos = b + unzigPtr[zig];
float val = *unzigPos;
val *= qtp[zig];
*unzigPos = val;
Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount);
}
}
@ -251,7 +217,7 @@ namespace ImageSharp.Formats
/// Copy raw 32bit floating point data to dest
/// </summary>
/// <param name="dest">Destination</param>
internal unsafe void CopyTo(MutableSpan<int> dest)
public unsafe void CopyTo(MutableSpan<int> dest)
{
fixed (Vector4* ptr = &this.V0L)
{
@ -264,160 +230,73 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Load raw 32bit floating point data from source
/// </summary>
/// <param name="source">Source</param>
internal unsafe void LoadFrom(MutableSpan<int> source)
{
fixed (Vector4* ptr = &this.V0L)
{
float* fp = (float*)ptr;
for (int i = 0; i < ScalarCount; i++)
{
fp[i] = source[i];
}
}
}
/// <summary>
/// Do IDCT internal operations on the left part of the block. Original source:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// Multiply all elements of the block.
/// </summary>
/// <param name="d">Destination block</param>
/// <param name="scaleVec">Vector to multiply by</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void IDCT8x4_LeftPart(ref Block8x8F d)
public void MultiplyAllInplace(Vector4 scaleVec)
{
Vector4 my1 = this.V1L;
Vector4 my7 = this.V7L;
Vector4 mz0 = my1 + my7;
Vector4 my3 = this.V3L;
Vector4 mz2 = my3 + my7;
Vector4 my5 = this.V5L;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = this.V2L;
Vector4 my6 = this.V6L;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = this.V0L;
Vector4 my4 = this.V4L;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
d.V0L = my0 + mb0;
d.V7L = my0 - mb0;
d.V1L = my1 + mb1;
d.V6L = my1 - mb1;
d.V2L = my2 + mb2;
d.V5L = my2 - mb2;
d.V3L = my3 + mb3;
d.V4L = my3 - mb3;
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;
}
/// <summary>
/// Do IDCT internal operations on the right part of the block.
/// Original source:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// Adds a vector to all elements of the block.
/// </summary>
/// <param name="destBlockPtr">Destination Block pointer</param>
/// <param name="diff">The added vector</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void IDCT8x4_RightPart(ref Block8x8F destBlockPtr)
public void AddToAllInplace(Vector4 diff)
{
Vector4 my1 = this.V1R;
Vector4 my7 = this.V7R;
Vector4 mz0 = my1 + my7;
Vector4 my3 = this.V3R;
Vector4 mz2 = my3 + my7;
Vector4 my5 = this.V5R;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = this.V2R;
Vector4 my6 = this.V6R;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = this.V0R;
Vector4 my4 = this.V4R;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
destBlockPtr.V0R = my0 + mb0;
destBlockPtr.V7R = my0 - mb0;
destBlockPtr.V1R = my1 + mb1;
destBlockPtr.V6R = my1 - mb1;
destBlockPtr.V2R = my2 + mb2;
destBlockPtr.V5R = my2 - mb2;
destBlockPtr.V3R = my3 + mb3;
destBlockPtr.V4R = my3 - mb3;
this.V0L += diff;
this.V0R += diff;
this.V1L += diff;
this.V1R += diff;
this.V2L += diff;
this.V2R += diff;
this.V3L += diff;
this.V3R += diff;
this.V4L += diff;
this.V4R += diff;
this.V5L += diff;
this.V5R += diff;
this.V6L += diff;
this.V6R += diff;
this.V7L += diff;
this.V7R += diff;
}
/// <summary>
/// Fill the block with defaults (zeroes)
/// Un-zig
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="qtPtr">Qt pointer</param>
/// <param name="unzigPtr">Unzig pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Clear()
public static unsafe void UnZig(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{
// The cheapest way to do this in C#:
this = default(Block8x8F);
}
/// <summary>
/// TODO: Should be removed when BlockF goes away
/// </summary>
/// <param name="legacyBlock">Legacy block</param>
internal void LoadFrom(ref BlockF legacyBlock)
{
this.LoadFrom(legacyBlock.Data);
}
/// <summary>
/// TODO: Should be removed when BlockF goes away
/// </summary>
/// <param name="legacyBlock">Legacy block</param>
internal void CopyTo(ref BlockF legacyBlock)
{
this.CopyTo(legacyBlock.Data);
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
for (int zig = 0; zig < ScalarCount; zig++)
{
float* unzigPos = b + unzigPtr[zig];
float val = *unzigPos;
val *= qtp[zig];
*unzigPos = val;
}
}
/// <summary>
@ -427,7 +306,7 @@ namespace ImageSharp.Formats
/// <param name="stride">Stride offset</param>
/// <param name="tempBlockPtr">Temp Block pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void CopyColorsTo(MutableSpan<byte> buffer, int stride, Block8x8F* tempBlockPtr)
public unsafe void CopyColorsTo(MutableSpan<byte> buffer, int stride, Block8x8F* tempBlockPtr)
{
this.TransformByteConvetibleColorValuesInto(ref *tempBlockPtr);
@ -446,5 +325,53 @@ namespace ImageSharp.Formats
src += 8;
}
}
/// <summary>
/// Unzig the elements of src into dest, while dividing them by elements of qt and rounding the values
/// </summary>
/// <param name="src">Source block</param>
/// <param name="dest">Destination block</param>
/// <param name="qt">Quantization table</param>
/// <param name="unzigPtr">Pointer to <see cref="UnzigData"/> elements</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void UnZigDivRound(Block8x8F* src, Block8x8F* dest, Block8x8F* qt, int* unzigPtr)
{
float* s = (float*)src;
float* d = (float*)dest;
float* q = (float*)qt;
for (int zig = 0; zig < ScalarCount; zig++)
{
float val = s[unzigPtr[zig]] / q[zig];
val = (int)val;
d[zig] = val;
}
}
/// <summary>
/// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block.
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="source">The source block.</param>
public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source)
{
float* d = (float*)destination;
for (int i = 0; i < 4; i++)
{
int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
float* iSource = (float*)(source + i);
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
int j = (16 * y) + (2 * x);
float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
d[(8 * y) + x + dstOff] = (sum + 2) / 4;
}
}
}
}
}
}

18
src/ImageSharp/Formats/Jpg/Components/BlockQuad.cs

@ -0,0 +1,18 @@
// <copyright file="BlockQuad.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg.Components
{
/// <summary>
/// Poor man's stackalloc: Contains a value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// Useful for decoder/encoder operations allocating a block for each Jpeg component.
/// </summary>
internal unsafe struct BlockQuad
{
/// <summary>
/// The value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// </summary>
public fixed float Data[4 * Block8x8F.ScalarCount];
}
}

340
src/ImageSharp/Formats/Jpg/Components/DCT.cs

@ -0,0 +1,340 @@
// <copyright file="DCT.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp.Formats.Jpg
{
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Contains forward and inverse DCT implementations
/// </summary>
internal static class DCT
{
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private static readonly Vector4 C_1_175876 = new Vector4(1.175876f);
private static readonly Vector4 C_1_961571 = new Vector4(-1.961571f);
private static readonly Vector4 C_0_390181 = new Vector4(-0.390181f);
private static readonly Vector4 C_0_899976 = new Vector4(-0.899976f);
private static readonly Vector4 C_2_562915 = new Vector4(-2.562915f);
private static readonly Vector4 C_0_298631 = new Vector4(0.298631f);
private static readonly Vector4 C_2_053120 = new Vector4(2.053120f);
private static readonly Vector4 C_3_072711 = new Vector4(3.072711f);
private static readonly Vector4 C_1_501321 = new Vector4(1.501321f);
private static readonly Vector4 C_0_541196 = new Vector4(0.541196f);
private static readonly Vector4 C_1_847759 = new Vector4(-1.847759f);
private static readonly Vector4 C_0_765367 = new Vector4(0.765367f);
private static readonly Vector4 C_0_125 = new Vector4(0.1250f);
#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f);
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
/// </summary>
/// <param name="src">Source</param>
/// <param name="dest">Destination</param>
/// <param name="temp">Temporary block provided by the caller</param>
public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp)
{
src.TransposeInto(ref temp);
IDCT8x4_LeftPart(ref temp, ref dest);
IDCT8x4_RightPart(ref temp, ref dest);
dest.TransposeInto(ref temp);
IDCT8x4_LeftPart(ref temp, ref dest);
IDCT8x4_RightPart(ref temp, ref dest);
dest.MultiplyAllInplace(C_0_125);
}
/// <summary>
/// Do IDCT internal operations on the left part of the block. Original src:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// </summary>
/// <param name="s">The source block</param>
/// <param name="d">Destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 my1 = s.V1L;
Vector4 my7 = s.V7L;
Vector4 mz0 = my1 + my7;
Vector4 my3 = s.V3L;
Vector4 mz2 = my3 + my7;
Vector4 my5 = s.V5L;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = s.V2L;
Vector4 my6 = s.V6L;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = s.V0L;
Vector4 my4 = s.V4L;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
d.V0L = my0 + mb0;
d.V7L = my0 - mb0;
d.V1L = my1 + mb1;
d.V6L = my1 - mb1;
d.V2L = my2 + mb2;
d.V5L = my2 - mb2;
d.V3L = my3 + mb3;
d.V4L = my3 - mb3;
}
/// <summary>
/// Do IDCT internal operations on the right part of the block.
/// Original src:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// </summary>
/// <param name="s">The source block</param>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 my1 = s.V1R;
Vector4 my7 = s.V7R;
Vector4 mz0 = my1 + my7;
Vector4 my3 = s.V3R;
Vector4 mz2 = my3 + my7;
Vector4 my5 = s.V5R;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = s.V2R;
Vector4 my6 = s.V6R;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = s.V0R;
Vector4 my4 = s.V4R;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
d.V0R = my0 + mb0;
d.V7R = my0 - mb0;
d.V1R = my1 + mb1;
d.V6R = my1 - mb1;
d.V2R = my2 + mb2;
d.V5R = my2 - mb2;
d.V3R = my3 + mb3;
d.V4R = my3 - mb3;
}
/// <summary>
/// Original:
/// <see>
/// <cref>https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15</cref>
/// </see>
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void FDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 c0 = s.V0L;
Vector4 c1 = s.V7L;
Vector4 t0 = c0 + c1;
Vector4 t7 = c0 - c1;
c1 = s.V6L;
c0 = s.V1L;
Vector4 t1 = c0 + c1;
Vector4 t6 = c0 - c1;
c1 = s.V5L;
c0 = s.V2L;
Vector4 t2 = c0 + c1;
Vector4 t5 = c0 - c1;
c0 = s.V3L;
c1 = s.V4L;
Vector4 t3 = c0 + c1;
Vector4 t4 = c0 - c1;
c0 = t0 + t3;
Vector4 c3 = t0 - t3;
c1 = t1 + t2;
Vector4 c2 = t1 - t2;
d.V0L = c0 + c1;
d.V4L = c0 - c1;
Vector4 w0 = new Vector4(0.541196f);
Vector4 w1 = new Vector4(1.306563f);
d.V2L = (w0 * c2) + (w1 * c3);
d.V6L = (w0 * c3) - (w1 * c2);
w0 = new Vector4(1.175876f);
w1 = new Vector4(0.785695f);
c3 = (w0 * t4) + (w1 * t7);
c0 = (w0 * t7) - (w1 * t4);
w0 = new Vector4(1.387040f);
w1 = new Vector4(0.275899f);
c2 = (w0 * t5) + (w1 * t6);
c1 = (w0 * t6) - (w1 * t5);
d.V3L = c0 - c2;
d.V5L = c3 - c1;
Vector4 invsqrt2 = new Vector4(0.707107f);
c0 = (c0 + c2) * invsqrt2;
c3 = (c3 + c1) * invsqrt2;
d.V1L = c0 + c3;
d.V7L = c0 - c3;
}
/// <summary>
/// Original:
/// <see>
/// <cref>https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15</cref>
/// </see>
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void FDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 c0 = s.V0R;
Vector4 c1 = s.V7R;
Vector4 t0 = c0 + c1;
Vector4 t7 = c0 - c1;
c1 = s.V6R;
c0 = s.V1R;
Vector4 t1 = c0 + c1;
Vector4 t6 = c0 - c1;
c1 = s.V5R;
c0 = s.V2R;
Vector4 t2 = c0 + c1;
Vector4 t5 = c0 - c1;
c0 = s.V3R;
c1 = s.V4R;
Vector4 t3 = c0 + c1;
Vector4 t4 = c0 - c1;
c0 = t0 + t3;
Vector4 c3 = t0 - t3;
c1 = t1 + t2;
Vector4 c2 = t1 - t2;
d.V0R = c0 + c1;
d.V4R = c0 - c1;
Vector4 w0 = new Vector4(0.541196f);
Vector4 w1 = new Vector4(1.306563f);
d.V2R = (w0 * c2) + (w1 * c3);
d.V6R = (w0 * c3) - (w1 * c2);
w0 = new Vector4(1.175876f);
w1 = new Vector4(0.785695f);
c3 = (w0 * t4) + (w1 * t7);
c0 = (w0 * t7) - (w1 * t4);
w0 = new Vector4(1.387040f);
w1 = new Vector4(0.275899f);
c2 = (w0 * t5) + (w1 * t6);
c1 = (w0 * t6) - (w1 * t5);
d.V3R = c0 - c2;
d.V5R = c3 - c1;
c0 = (c0 + c2) * InvSqrt2;
c3 = (c3 + c1) * InvSqrt2;
d.V1R = c0 + c3;
d.V7R = c0 - c3;
}
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
/// </summary>
/// <param name="src">Source</param>
/// <param name="dest">Destination</param>
/// <param name="temp">Temporary block provided by the caller</param>
/// <param name="offsetSourceByNeg128">If true, a constant -128.0 offset is applied for all values before FDCT </param>
public static void TransformFDCT(
ref Block8x8F src,
ref Block8x8F dest,
ref Block8x8F temp,
bool offsetSourceByNeg128 = true)
{
src.TransposeInto(ref temp);
if (offsetSourceByNeg128)
{
temp.AddToAllInplace(new Vector4(-128));
}
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
dest.TransposeInto(ref temp);
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
dest.MultiplyAllInplace(C_0_125);
}
}
}

6
src/ImageSharp/Formats/Jpg/Components/Bits.cs → src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
using System.Runtime.CompilerServices;
@ -47,7 +47,7 @@ namespace ImageSharp.Formats
// Grab the decode bytes, use them and then set them
// back on the decoder.
var decoderBytes = decoder.Bytes;
Bytes decoderBytes = decoder.Bytes;
byte c = decoderBytes.ReadByteStuffedByte(decoder.InputStream, out errorCode);
decoder.Bytes = decoderBytes;
@ -84,7 +84,7 @@ namespace ImageSharp.Formats
{
if (this.UnreadBits < t)
{
var errorCode = this.EnsureNBits(t, decoder);
JpegDecoderCore.ErrorCodes errorCode = this.EnsureNBits(t, decoder);
if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
{
throw new JpegDecoderCore.MissingFF00Exception();

2
src/ImageSharp/Formats/Jpg/Components/Bytes.cs → src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs

@ -2,7 +2,7 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Buffers;

2
src/ImageSharp/Formats/Jpg/Components/Component.cs → src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
/// <summary>
/// Represents a single color component

2
src/ImageSharp/Formats/Jpg/Components/GrayImage.cs → src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
/// <summary>
/// Represents a grayscale image

6
src/ImageSharp/Formats/Jpg/Components/Huffman.cs → src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs

@ -1,8 +1,8 @@
// <copyright file="Huffman.cs" company="James Jackson-South">
// <copyright file="HuffmanTree.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Buffers;
@ -10,7 +10,7 @@ namespace ImageSharp.Formats
/// <summary>
/// Represents a Huffman tree
/// </summary>
internal struct Huffman : IDisposable
internal struct HuffmanTree : IDisposable
{
/// <summary>
/// Gets or sets the number of codes in the tree.

2
src/ImageSharp/Formats/Jpg/Components/YCbCrImage.cs → src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
/// <summary>
/// Represents an image made up of three color components (luminance, blue chroma, red chroma)

36
src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs

@ -0,0 +1,36 @@
// <copyright file="HuffIndex.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
/// <summary>
/// Enumerates the Huffman tables
/// </summary>
internal enum HuffIndex
{
/// <summary>
/// The DC luminance huffman table index
/// </summary>
LuminanceDC = 0,
// ReSharper disable UnusedMember.Local
/// <summary>
/// The AC luminance huffman table index
/// </summary>
LuminanceAC = 1,
/// <summary>
/// The DC chrominance huffman table index
/// </summary>
ChrominanceDC = 2,
/// <summary>
/// The AC chrominance huffman table index
/// </summary>
ChrominanceAC = 3,
// ReSharper restore UnusedMember.Local
}
}

73
src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs

@ -0,0 +1,73 @@
// <copyright file="HuffmanLut.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
/// <summary>
/// A compiled look-up table representation of a huffmanSpec.
/// Each value maps to a uint32 of which the 8 most significant bits hold the
/// codeword size in bits and the 24 least significant bits hold the codeword.
/// The maximum codeword size is 16 bits.
/// </summary>
internal struct HuffmanLut
{
/// <summary>
/// The compiled representations of theHuffmanSpec.
/// </summary>
public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4];
/// <summary>
/// Initializes static members of the <see cref="HuffmanLut"/> struct.
/// </summary>
static HuffmanLut()
{
// Initialize the Huffman tables
for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++)
{
TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanLut"/> struct.
/// </summary>
/// <param name="spec">dasd</param>
public HuffmanLut(HuffmanSpec spec)
{
int maxValue = 0;
foreach (byte v in spec.Values)
{
if (v > maxValue)
{
maxValue = v;
}
}
this.Values = new uint[maxValue + 1];
int code = 0;
int k = 0;
for (int i = 0; i < spec.Count.Length; i++)
{
int bits = (i + 1) << 24;
for (int j = 0; j < spec.Count[i]; j++)
{
this.Values[spec.Values[k]] = (uint)(bits | code);
code++;
k++;
}
code <<= 1;
}
}
/// <summary>
/// Gets the collection of huffman values.
/// </summary>
public uint[] Values { get; }
}
}

136
src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs

@ -0,0 +1,136 @@
// <copyright file="HuffmanSpec.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
/// <summary>
/// The Huffman encoding specifications.
/// </summary>
internal struct HuffmanSpec
{
#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines
/// <summary>
/// The Huffman encoding specifications.
/// This encoder uses the same Huffman encoding for all images.
/// </summary>
public static readonly HuffmanSpec[] TheHuffmanSpecs =
{
// Luminance DC.
new HuffmanSpec(
new byte[]
{
0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
new HuffmanSpec(
new byte[]
{
0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0,
0, 1, 125
},
new byte[]
{
0x01, 0x02, 0x03, 0x00, 0x04, 0x11,
0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13,
0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,
0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1,
0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56,
0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4,
0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
}),
new HuffmanSpec(
new byte[]
{
0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
// Chrominance AC.
new HuffmanSpec(
new byte[]
{
0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0,
1, 2, 119
},
new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81,
0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1,
0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62,
0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64,
0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73,
0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2,
0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3,
0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
})
};
#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines
/// <summary>
/// Gets count[i] - The number of codes of length i bits.
/// </summary>
public readonly byte[] Count;
/// <summary>
/// Gets value[i] - The decoded value of the codeword at the given index.
/// </summary>
public readonly byte[] Values;
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanSpec"/> struct.
/// </summary>
/// <param name="count">
/// The number of codes.
/// </param>
/// <param name="values">
/// The decoded values.
/// </param>
public HuffmanSpec(byte[] count, byte[] values)
{
this.Count = count;
this.Values = values;
}
}
}

22
src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs

@ -0,0 +1,22 @@
// <copyright file="QuantIndex.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
/// <summary>
/// Enumerates the quantization tables
/// </summary>
internal enum QuantIndex
{
/// <summary>
/// The luminance quantization table index
/// </summary>
Luminance = 0,
/// <summary>
/// The chrominance quantization table index
/// </summary>
Chrominance = 1,
}
}

162
src/ImageSharp/Formats/Jpg/Components/FDCT.cs

@ -1,162 +0,0 @@
// <copyright file="FDCT.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Performs a fast, forward discrete cosine transform against the given block
/// decomposing it into 64 orthogonal basis signals.
/// </summary>
internal class FDCT
{
// Trigonometric constants in 13-bit fixed point format.
// TODO: Rename and describe these.
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private const int Fix_0_298631336 = 2446;
private const int Fix_0_390180644 = 3196;
private const int Fix_0_541196100 = 4433;
private const int Fix_0_765366865 = 6270;
private const int Fix_0_899976223 = 7373;
private const int Fix_1_175875602 = 9633;
private const int Fix_1_501321110 = 12299;
private const int Fix_1_847759065 = 15137;
private const int Fix_1_961570560 = 16069;
private const int Fix_2_053119869 = 16819;
private const int Fix_2_562915447 = 20995;
private const int Fix_3_072711026 = 25172;
#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
/// <summary>
/// The number of bits
/// </summary>
private const int Bits = 13;
/// <summary>
/// The number of bits to shift by on the first pass.
/// </summary>
private const int Pass1Bits = 2;
/// <summary>
/// The value to shift by
/// </summary>
private const int CenterJSample = 128;
/// <summary>
/// Performs a forward DCT on an 8x8 block of coefficients, including a level shift.
/// </summary>
/// <param name="block">The block of coefficients.</param>
public static void Transform(ref Block block)
{
// Pass 1: process rows.
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
int x0 = block[y8];
int x1 = block[y8 + 1];
int x2 = block[y8 + 2];
int x3 = block[y8 + 3];
int x4 = block[y8 + 4];
int x5 = block[y8 + 5];
int x6 = block[y8 + 6];
int x7 = block[y8 + 7];
int tmp0 = x0 + x7;
int tmp1 = x1 + x6;
int tmp2 = x2 + x5;
int tmp3 = x3 + x4;
int tmp10 = tmp0 + tmp3;
int tmp12 = tmp0 - tmp3;
int tmp11 = tmp1 + tmp2;
int tmp13 = tmp1 - tmp2;
tmp0 = x0 - x7;
tmp1 = x1 - x6;
tmp2 = x2 - x5;
tmp3 = x3 - x4;
block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits;
block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits;
int z1 = (tmp12 + tmp13) * Fix_0_541196100;
z1 += 1 << (Bits - Pass1Bits - 1);
block[y8 + 2] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits - Pass1Bits);
block[y8 + 6] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits - Pass1Bits);
tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp0 + tmp2;
tmp13 = tmp1 + tmp3;
z1 = (tmp12 + tmp13) * Fix_1_175875602;
z1 += 1 << (Bits - Pass1Bits - 1);
tmp0 = tmp0 * Fix_1_501321110;
tmp1 = tmp1 * Fix_3_072711026;
tmp2 = tmp2 * Fix_2_053119869;
tmp3 = tmp3 * Fix_0_298631336;
tmp10 = tmp10 * -Fix_0_899976223;
tmp11 = tmp11 * -Fix_2_562915447;
tmp12 = tmp12 * -Fix_0_390180644;
tmp13 = tmp13 * -Fix_1_961570560;
tmp12 += z1;
tmp13 += z1;
block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits);
block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits);
block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits);
block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits);
}
// Pass 2: process columns.
// We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8.
for (int x = 0; x < 8; x++)
{
int tmp0 = block[x] + block[56 + x];
int tmp1 = block[8 + x] + block[48 + x];
int tmp2 = block[16 + x] + block[40 + x];
int tmp3 = block[24 + x] + block[32 + x];
int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1));
int tmp12 = tmp0 - tmp3;
int tmp11 = tmp1 + tmp2;
int tmp13 = tmp1 - tmp2;
tmp0 = block[x] - block[56 + x];
tmp1 = block[8 + x] - block[48 + x];
tmp2 = block[16 + x] - block[40 + x];
tmp3 = block[24 + x] - block[32 + x];
block[x] = (tmp10 + tmp11) >> Pass1Bits;
block[32 + x] = (tmp10 - tmp11) >> Pass1Bits;
int z1 = (tmp12 + tmp13) * Fix_0_541196100;
z1 += 1 << (Bits + Pass1Bits - 1);
block[16 + x] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits + Pass1Bits);
block[48 + x] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits + Pass1Bits);
tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp0 + tmp2;
tmp13 = tmp1 + tmp3;
z1 = (tmp12 + tmp13) * Fix_1_175875602;
z1 += 1 << (Bits + Pass1Bits - 1);
tmp0 = tmp0 * Fix_1_501321110;
tmp1 = tmp1 * Fix_3_072711026;
tmp2 = tmp2 * Fix_2_053119869;
tmp3 = tmp3 * Fix_0_298631336;
tmp10 = tmp10 * -Fix_0_899976223;
tmp11 = tmp11 * -Fix_2_562915447;
tmp12 = tmp12 * -Fix_0_390180644;
tmp13 = tmp13 * -Fix_1_961570560;
tmp12 += z1;
tmp13 += z1;
block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits);
block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits);
block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits);
block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits);
}
}
}
}

169
src/ImageSharp/Formats/Jpg/Components/IDCT.cs

@ -1,169 +0,0 @@
// <copyright file="IDCT.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Performs a 2-D Inverse Discrete Cosine Transformation.
/// </summary>
internal class IDCT
{
private const int W1 = 2841; // 2048*sqrt(2)*cos(1*pi/16)
private const int W2 = 2676; // 2048*sqrt(2)*cos(2*pi/16)
private const int W3 = 2408; // 2048*sqrt(2)*cos(3*pi/16)
private const int W5 = 1609; // 2048*sqrt(2)*cos(5*pi/16)
private const int W6 = 1108; // 2048*sqrt(2)*cos(6*pi/16)
private const int W7 = 565; // 2048*sqrt(2)*cos(7*pi/16)
private const int W1pw7 = W1 + W7;
private const int W1mw7 = W1 - W7;
private const int W2pw6 = W2 + W6;
private const int W2mw6 = W2 - W6;
private const int W3pw5 = W3 + W5;
private const int W3mw5 = W3 - W5;
private const int R2 = 181; // 256/sqrt(2)
/// <summary>
/// Performs a 2-D Inverse Discrete Cosine Transformation.
/// <para>
/// The input coefficients should already have been multiplied by the
/// appropriate quantization table. We use fixed-point computation, with the
/// number of bits for the fractional component varying over the intermediate
/// stages.
/// </para>
/// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the
/// discrete W transform and for the discrete Fourier transform", IEEE Trans. on
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
/// </summary>
/// <param name="src">The source block of coefficients</param>
public static void Transform(ref Block src)
{
// Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
// If all the AC components are zero, then the IDCT is trivial.
if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 &&
src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0)
{
int dc = src[y8 + 0] << 3;
src[y8 + 0] = dc;
src[y8 + 1] = dc;
src[y8 + 2] = dc;
src[y8 + 3] = dc;
src[y8 + 4] = dc;
src[y8 + 5] = dc;
src[y8 + 6] = dc;
src[y8 + 7] = dc;
continue;
}
// Prescale.
int x0 = (src[y8 + 0] << 11) + 128;
int x1 = src[y8 + 4] << 11;
int x2 = src[y8 + 6];
int x3 = src[y8 + 2];
int x4 = src[y8 + 1];
int x5 = src[y8 + 7];
int x6 = src[y8 + 5];
int x7 = src[y8 + 3];
// Stage 1.
int x8 = W7 * (x4 + x5);
x4 = x8 + (W1mw7 * x4);
x5 = x8 - (W1pw7 * x5);
x8 = W3 * (x6 + x7);
x6 = x8 - (W3mw5 * x6);
x7 = x8 - (W3pw5 * x7);
// Stage 2.
x8 = x0 + x1;
x0 -= x1;
x1 = W6 * (x3 + x2);
x2 = x1 - (W2pw6 * x2);
x3 = x1 + (W2mw6 * x3);
x1 = x4 + x6;
x4 -= x6;
x6 = x5 + x7;
x5 -= x7;
// Stage 3.
x7 = x8 + x3;
x8 -= x3;
x3 = x0 + x2;
x0 -= x2;
x2 = ((R2 * (x4 + x5)) + 128) >> 8;
x4 = ((R2 * (x4 - x5)) + 128) >> 8;
// Stage 4.
src[y8 + 0] = (x7 + x1) >> 8;
src[y8 + 1] = (x3 + x2) >> 8;
src[y8 + 2] = (x0 + x4) >> 8;
src[y8 + 3] = (x8 + x6) >> 8;
src[y8 + 4] = (x8 - x6) >> 8;
src[y8 + 5] = (x0 - x4) >> 8;
src[y8 + 6] = (x3 - x2) >> 8;
src[y8 + 7] = (x7 - x1) >> 8;
}
// Vertical 1-D IDCT.
for (int x = 0; x < 8; x++)
{
// Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial.
// However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so
// we do not bother to check for the all-zero case.
// Prescale.
int y0 = (src[x] << 8) + 8192;
int y1 = src[32 + x] << 8;
int y2 = src[48 + x];
int y3 = src[16 + x];
int y4 = src[8 + x];
int y5 = src[56 + x];
int y6 = src[40 + x];
int y7 = src[24 + x];
// Stage 1.
int y8 = (W7 * (y4 + y5)) + 4;
y4 = (y8 + (W1mw7 * y4)) >> 3;
y5 = (y8 - (W1pw7 * y5)) >> 3;
y8 = (W3 * (y6 + y7)) + 4;
y6 = (y8 - (W3mw5 * y6)) >> 3;
y7 = (y8 - (W3pw5 * y7)) >> 3;
// Stage 2.
y8 = y0 + y1;
y0 -= y1;
y1 = (W6 * (y3 + y2)) + 4;
y2 = (y1 - (W2pw6 * y2)) >> 3;
y3 = (y1 + (W2mw6 * y3)) >> 3;
y1 = y4 + y6;
y4 -= y6;
y6 = y5 + y7;
y5 -= y7;
// Stage 3.
y7 = y8 + y3;
y8 -= y3;
y3 = y0 + y2;
y0 -= y2;
y2 = ((R2 * (y4 + y5)) + 128) >> 8;
y4 = ((R2 * (y4 - y5)) + 128) >> 8;
// Stage 4.
src[x] = (y7 + y1) >> 14;
src[8 + x] = (y3 + y2) >> 14;
src[16 + x] = (y0 + y4) >> 14;
src[24 + x] = (y8 + y6) >> 14;
src[32 + x] = (y8 - y6) >> 14;
src[40 + x] = (y0 - y4) >> 14;
src[48 + x] = (y3 - y2) >> 14;
src[56 + x] = (y7 - y1) >> 14;
}
}
}
}

81
src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs

@ -1,81 +0,0 @@
// <copyright file="MutableSpanExtensions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg.Components
{
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// MutableSpan Extensions
/// </summary>
internal static class MutableSpanExtensions
{
/// <summary>
/// Slice <see cref="MutableSpan{T}"/>
/// </summary>
/// <typeparam name="T">The type of the data in the span</typeparam>
/// <param name="array">The data array</param>
/// <param name="offset">The offset</param>
/// <returns>The new <see cref="MutableSpan{T}"/></returns>
public static MutableSpan<T> Slice<T>(this T[] array, int offset) => new MutableSpan<T>(array, offset);
/// <summary>
/// Save to a Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to save to</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SaveTo(this MutableSpan<float> data, ref Vector4 v)
{
v.X = data[0];
v.Y = data[1];
v.Z = data[2];
v.W = data[3];
}
/// <summary>
/// Save to a Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to save to</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SaveTo(this MutableSpan<int> data, ref Vector4 v)
{
v.X = data[0];
v.Y = data[1];
v.Z = data[2];
v.W = data[3];
}
/// <summary>
/// Load from Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to load from</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LoadFrom(this MutableSpan<float> data, ref Vector4 v)
{
data[0] = v.X;
data[1] = v.Y;
data[2] = v.Z;
data[3] = v.W;
}
/// <summary>
/// Load from Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to load from</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LoadFrom(this MutableSpan<int> data, ref Vector4 v)
{
data[0] = (int)v.X;
data[1] = (int)v.Y;
data[2] = (int)v.Z;
data[3] = (int)v.W;
}
}
}

127
src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs

@ -10,6 +10,7 @@ namespace ImageSharp.Formats
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using ImageSharp.Formats.Jpg;
/// <summary>
/// Performs the jpeg decoding operation.
@ -63,19 +64,6 @@ namespace ImageSharp.Formats
/// </summary>
private const int AcTable = 1;
/// <summary>
/// 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>
private static readonly int[] Unzig =
{
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33,
40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50,
43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
};
/// <summary>
/// The component array
/// </summary>
@ -89,7 +77,7 @@ namespace ImageSharp.Formats
/// <summary>
/// The huffman trees
/// </summary>
private readonly Huffman[] huffmanTrees;
private readonly HuffmanTree[] huffmanTrees;
/// <summary>
/// Quantization tables, in zigzag order.
@ -199,10 +187,10 @@ namespace ImageSharp.Formats
public JpegDecoderCore()
{
// this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
this.huffmanTrees = new Huffman[(MaxTc + 1) * (MaxTh + 1)];
this.huffmanTrees = new HuffmanTree[(MaxTc + 1) * (MaxTh + 1)];
this.quantizationTables = new Block8x8F[MaxTq + 1];
this.temp = new byte[2 * BlockF.BlockSize];
this.temp = new byte[2 * Block8x8F.ScalarCount];
this.componentArray = new Component[MaxComponents];
this.progCoeffs = new Block8x8F[MaxComponents][];
this.bits = default(Bits);
@ -559,42 +547,42 @@ namespace ImageSharp.Formats
}
}
private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining)
private void ProcessDefineHuffmanTablesMarkerLoop(ref HuffmanTree huffmanTree, ref int remaining)
{
// Read nCodes and huffman.Valuess (and derive h.Length).
// nCodes[i] is the number of codes with code length i.
// h.Length is the total number of codes.
huffman.Length = 0;
huffmanTree.Length = 0;
int[] ncodes = new int[MaxCodeLength];
for (int i = 0; i < ncodes.Length; i++)
{
ncodes[i] = this.temp[i + 1];
huffman.Length += ncodes[i];
huffmanTree.Length += ncodes[i];
}
if (huffman.Length == 0)
if (huffmanTree.Length == 0)
{
throw new ImageFormatException("Huffman table has zero length");
}
if (huffman.Length > MaxNCodes)
if (huffmanTree.Length > MaxNCodes)
{
throw new ImageFormatException("Huffman table has excessive length");
}
remaining -= huffman.Length + 17;
remaining -= huffmanTree.Length + 17;
if (remaining < 0)
{
throw new ImageFormatException("DHT has wrong length");
}
this.ReadFull(huffman.Values, 0, huffman.Length);
this.ReadFull(huffmanTree.Values, 0, huffmanTree.Length);
// Derive the look-up table.
for (int i = 0; i < huffman.Lut.Length; i++)
for (int i = 0; i < huffmanTree.Lut.Length; i++)
{
huffman.Lut[i] = 0;
huffmanTree.Lut[i] = 0;
}
uint x = 0, code = 0;
@ -611,11 +599,11 @@ namespace ImageSharp.Formats
// The high 8 bits of lutValue are the encoded value.
// The low 8 bits are 1 plus the codeLength.
byte base2 = (byte)(code << (7 - i));
ushort lutValue = (ushort)((huffman.Values[x] << 8) | (2 + i));
ushort lutValue = (ushort)((huffmanTree.Values[x] << 8) | (2 + i));
for (int k = 0; k < 1 << (7 - i); k++)
{
huffman.Lut[base2 | k] = lutValue;
huffmanTree.Lut[base2 | k] = lutValue;
}
code++;
@ -630,15 +618,15 @@ namespace ImageSharp.Formats
int nc = ncodes[i];
if (nc == 0)
{
huffman.MinCodes[i] = -1;
huffman.MaxCodes[i] = -1;
huffman.Indices[i] = -1;
huffmanTree.MinCodes[i] = -1;
huffmanTree.MaxCodes[i] = -1;
huffmanTree.Indices[i] = -1;
}
else
{
huffman.MinCodes[i] = c;
huffman.MaxCodes[i] = c + nc - 1;
huffman.Indices[i] = index;
huffmanTree.MinCodes[i] = c;
huffmanTree.MaxCodes[i] = c + nc - 1;
huffmanTree.Indices[i] = index;
c += nc;
index += nc;
}
@ -650,23 +638,23 @@ namespace ImageSharp.Formats
/// <summary>
/// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
/// </summary>
/// <param name="huffman">The huffman value</param>
/// <param name="huffmanTree">The huffman value</param>
/// <returns>The <see cref="byte"/></returns>
private byte DecodeHuffman(ref Huffman huffman)
private byte DecodeHuffman(ref HuffmanTree huffmanTree)
{
// Copy stuff to the stack:
if (huffman.Length == 0)
if (huffmanTree.Length == 0)
{
throw new ImageFormatException("Uninitialized Huffman table");
}
if (this.bits.UnreadBits < 8)
{
var errorCode = this.bits.EnsureNBits(8, this);
ErrorCodes errorCode = this.bits.EnsureNBits(8, this);
if (errorCode == ErrorCodes.NoError)
{
ushort v = huffman.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff];
ushort v = huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff];
if (v != 0)
{
@ -687,7 +675,7 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits == 0)
{
var errorCode = this.bits.EnsureNBits(1, this);
ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
if (errorCode != ErrorCodes.NoError)
{
throw new MissingFF00Exception();
@ -702,9 +690,9 @@ namespace ImageSharp.Formats
this.bits.UnreadBits--;
this.bits.Mask >>= 1;
if (code <= huffman.MaxCodes[i])
if (code <= huffmanTree.MaxCodes[i])
{
return huffman.Values[huffman.Indices[i] + code - huffman.MinCodes[i]];
return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]];
}
code <<= 1;
@ -721,7 +709,7 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits == 0)
{
var errorCode = this.bits.EnsureNBits(1, this);
ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
if (errorCode != ErrorCodes.NoError)
{
throw new MissingFF00Exception();
@ -743,7 +731,7 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits < count)
{
var errorCode = this.bits.EnsureNBits(count, this);
ErrorCodes errorCode = this.bits.EnsureNBits(count, this);
if (errorCode != ErrorCodes.NoError)
{
throw new MissingFF00Exception();
@ -1066,32 +1054,32 @@ namespace ImageSharp.Formats
switch (x >> 4)
{
case 0:
if (remaining < BlockF.BlockSize)
if (remaining < Block8x8F.ScalarCount)
{
done = true;
break;
}
remaining -= BlockF.BlockSize;
this.ReadFull(this.temp, 0, BlockF.BlockSize);
remaining -= Block8x8F.ScalarCount;
this.ReadFull(this.temp, 0, Block8x8F.ScalarCount);
for (int i = 0; i < BlockF.BlockSize; i++)
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
this.quantizationTables[tq][i] = this.temp[i];
}
break;
case 1:
if (remaining < 2 * BlockF.BlockSize)
if (remaining < 2 * Block8x8F.ScalarCount)
{
done = true;
break;
}
remaining -= 2 * BlockF.BlockSize;
this.ReadFull(this.temp, 0, 2 * BlockF.BlockSize);
remaining -= 2 * Block8x8F.ScalarCount;
this.ReadFull(this.temp, 0, 2 * Block8x8F.ScalarCount);
for (int i = 0; i < BlockF.BlockSize; i++)
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
this.quantizationTables[tq][i] = (this.temp[2 * i] << 8) | this.temp[(2 * i) + 1];
}
@ -1486,7 +1474,7 @@ namespace ImageSharp.Formats
// significant bit.
// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
int zigStart = 0;
int zigEnd = BlockF.BlockSize - 1;
int zigEnd = Block8x8F.ScalarCount - 1;
int ah = 0;
int al = 0;
@ -1497,7 +1485,7 @@ namespace ImageSharp.Formats
ah = this.temp[3 + scanComponentCountX2] >> 4;
al = this.temp[3 + scanComponentCountX2] & 0x0f;
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= BlockF.BlockSize)
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= Block8x8F.ScalarCount)
{
throw new ImageFormatException("Bad spectral selection bounds");
}
@ -1531,7 +1519,7 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index;
if (this.progCoeffs[compIndex] == null)
{
var size = mxx * myy * this.componentArray[compIndex].HorizontalFactor
int size = mxx * myy * this.componentArray[compIndex].HorizontalFactor
* this.componentArray[compIndex].VerticalFactor;
this.progCoeffs[compIndex] = new Block8x8F[size];
@ -1556,10 +1544,9 @@ namespace ImageSharp.Formats
Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F);
// Tricky way to copy contents of the Unzig static variable to the stack:
StackallocUnzigData unzigOnStack = default(StackallocUnzigData);
int* unzigPtr = unzigOnStack.Data;
Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
UnzigData unzig = UnzigData.Create();
int* unzigPtr = unzig.Data;
for (int my = 0; my < myy; my++)
{
@ -1612,7 +1599,7 @@ namespace ImageSharp.Formats
}
}
var qtIndex = this.componentArray[compIndex].Selector;
int qtIndex = this.componentArray[compIndex].Selector;
// TODO: Find a way to clean up this mess
fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex])
@ -1727,7 +1714,7 @@ namespace ImageSharp.Formats
int bx,
Block8x8F* qt)
{
var huffmannIdx = (AcTable * ThRowSize) + scan[i].AcTableSelector;
int huffmannIdx = (AcTable * ThRowSize) + scan[i].AcTableSelector;
if (ah != 0)
{
this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al);
@ -1802,7 +1789,7 @@ namespace ImageSharp.Formats
if (this.isProgressive)
{
if (zigEnd != BlockF.BlockSize - 1 || al != 0)
if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0)
{
// We haven't completely decoded this 8x8 block. Save the coefficients.
@ -1823,7 +1810,7 @@ namespace ImageSharp.Formats
// Dequantize, perform the inverse DCT and store the block to the image.
Block8x8F.UnZig(b, qt, unzigPtr);
b->TransformIDCTInto(ref *temp1, ref *temp2);
DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2);
byte[] dst;
int offset;
@ -1941,7 +1928,7 @@ namespace ImageSharp.Formats
/// <param name="zigStart">The zig-zag start index</param>
/// <param name="zigEnd">The zig-zag end index</param>
/// <param name="delta">The low transform offset</param>
private void Refine(Block8x8F* b, ref Huffman h, int* unzigPtr, int zigStart, int zigEnd, int delta)
private void Refine(Block8x8F* b, ref HuffmanTree h, int* unzigPtr, int zigStart, int zigEnd, int delta)
{
// Refining a DC component is trivial.
if (zigStart == 0)
@ -2013,7 +2000,7 @@ namespace ImageSharp.Formats
int blah = zig;
zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta);
zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta, unzigPtr);
if (zig > zigEnd)
{
throw new ImageFormatException($"Too many coefficients {zig} > {zigEnd}");
@ -2030,7 +2017,7 @@ namespace ImageSharp.Formats
if (this.eobRun > 0)
{
this.eobRun--;
this.RefineNonZeroes(b, zig, zigEnd, -1, delta);
this.RefineNonZeroes(b, zig, zigEnd, -1, delta, unzigPtr);
}
}
@ -2043,12 +2030,13 @@ namespace ImageSharp.Formats
/// <param name="zigEnd">The zig-zag end index</param>
/// <param name="nz">The non-zero entry</param>
/// <param name="delta">The low transform offset</param>
/// <param name="unzigPtr">Pointer to the Jpeg Unzig data (data part of <see cref="UnzigData"/>)</param>
/// <returns>The <see cref="int"/></returns>
private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta)
private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta, int* unzigPtr)
{
for (; zig <= zigEnd; zig++)
{
int u = Unzig[zig];
int u = unzigPtr[zig];
float bu = Block8x8F.GetScalarAt(b, u);
// TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary?
@ -2247,11 +2235,6 @@ namespace ImageSharp.Formats
public byte AcTableSelector { get; set; }
}
private struct StackallocUnzigData
{
internal fixed int Data[64];
}
/// <summary>
/// The missing ff00 exception.
/// </summary>

965
src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs

File diff suppressed because it is too large

48
src/ImageSharp/Formats/Jpg/UnzigData.cs

@ -0,0 +1,48 @@
// <copyright file="UnzigData.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Runtime.InteropServices;
/// <summary>
/// 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
{
/// <summary>
/// Copy of <see cref="Unzig"/> in a value type
/// </summary>
public fixed int Data[64];
/// <summary>
/// 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>
private static readonly int[] Unzig =
{
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33,
40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50,
43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
};
/// <summary>
/// Creates and fills an instance of <see cref="UnzigData"/> with Jpeg unzig indices
/// </summary>
/// <returns>The new instance</returns>
public static UnzigData Create()
{
UnzigData result = default(UnzigData);
int* unzigPtr = result.Data;
Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
return result;
}
}
}

123
src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs

@ -0,0 +1,123 @@
// <copyright file="JpegUtils.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Runtime.CompilerServices;
/// <summary>
/// Jpeg specific utilities and extension methods
/// </summary>
internal static unsafe class JpegUtils
{
/// <summary>
/// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of the image.
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
/// <param name="pixels">The input pixel acessor</param>
/// <param name="dest">The destination <see cref="PixelArea{TColor}"/></param>
/// <param name="sourceY">Starting Y coord</param>
/// <param name="sourceX">Starting X coord</param>
public static void CopyRGBBytesStretchedTo<TColor>(
this PixelAccessor<TColor> pixels,
PixelArea<TColor> dest,
int sourceY,
int sourceX)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
pixels.UncheckedCopyTo(dest, sourceY, sourceX);
int stretchFromX = pixels.Width - sourceX;
int stretchFromY = pixels.Height - sourceY;
StretchPixels(dest, stretchFromX, stretchFromY);
}
/// <summary>
/// Copy a region of image into the image destination area. Does not throw when requesting a 0-size copy.
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
/// <param name="sourcePixels">The source <see cref="PixelAccessor{TColor}"/> </param>
/// <param name="destinationArea">The destination area.</param>
/// <param name="sourceY">The source row index.</param>
/// <param name="sourceX">The source column index.</param>
/// <exception cref="NotSupportedException">
/// Thrown when an unsupported component order value is passed.
/// </exception>
public static void UncheckedCopyTo<TColor>(
this PixelAccessor<TColor> sourcePixels,
PixelArea<TColor> destinationArea,
int sourceY,
int sourceX)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
// TODO: Code smell! This is exactly the same code PixelArea<TColor>.CopyTo() starts with!
int width = Math.Min(destinationArea.Width, sourcePixels.Width - sourceX);
int height = Math.Min(destinationArea.Height, sourcePixels.Height - sourceY);
if (width < 1 || height < 1)
{
return;
}
sourcePixels.CopyTo(destinationArea, sourceY, sourceX);
}
/// <summary>
/// Copy an RGB value
/// </summary>
/// <param name="source">Source pointer</param>
/// <param name="dest">Destination pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyRgb(byte* source, byte* dest)
{
*dest++ = *source++; // R
*dest++ = *source++; // G
*dest = *source; // B
}
// Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsInvalidStretchStartingPosition<TColor>(PixelArea<TColor> area, int fromX, int fromY)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height;
}
private static void StretchPixels<TColor>(PixelArea<TColor> area, int fromX, int fromY)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
if (IsInvalidStretchStartingPosition(area, fromX, fromY))
{
return;
}
for (int y = 0; y < fromY; y++)
{
byte* ptrBase = (byte*)area.DataPointer + (y * area.RowByteCount);
for (int x = fromX; x < area.Width; x++)
{
byte* prevPtr = ptrBase + ((x - 1) * 3);
byte* currPtr = ptrBase + (x * 3);
CopyRgb(prevPtr, currPtr);
}
}
for (int y = fromY; y < area.Height; y++)
{
byte* currBase = (byte*)area.DataPointer + (y * area.RowByteCount);
byte* prevBase = (byte*)area.DataPointer + ((y - 1) * area.RowByteCount);
for (int x = 0; x < area.Width; x++)
{
int x3 = 3 * x;
byte* currPtr = currBase + x3;
byte* prevPtr = prevBase + x3;
CopyRgb(prevPtr, currPtr);
}
}
}
}
}

2
src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs → src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
namespace ImageSharp.Formats.Jpg
{
using System.Runtime.CompilerServices;

164
src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs

@ -0,0 +1,164 @@
// <copyright file="MutableSpanExtensions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// MutableSpan Extensions
/// </summary>
internal static class MutableSpanExtensions
{
/// <summary>
/// Slice <see cref="MutableSpan{T}"/>
/// </summary>
/// <typeparam name="T">The type of the data in the span</typeparam>
/// <param name="array">The data array</param>
/// <param name="offset">The offset</param>
/// <returns>The new <see cref="MutableSpan{T}"/></returns>
public static MutableSpan<T> Slice<T>(this T[] array, int offset) => new MutableSpan<T>(array, offset);
/// <summary>
/// Save to a Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to save to</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SaveTo(this MutableSpan<float> data, ref Vector4 v)
{
v.X = data[0];
v.Y = data[1];
v.Z = data[2];
v.W = data[3];
}
/// <summary>
/// Save to a Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to save to</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SaveTo(this MutableSpan<int> data, ref Vector4 v)
{
v.X = data[0];
v.Y = data[1];
v.Z = data[2];
v.W = data[3];
}
/// <summary>
/// Load from Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to load from</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LoadFrom(this MutableSpan<float> data, ref Vector4 v)
{
data[0] = v.X;
data[1] = v.Y;
data[2] = v.Z;
data[3] = v.W;
}
/// <summary>
/// Load from Vector4
/// </summary>
/// <param name="data">The data</param>
/// <param name="v">The vector to load from</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LoadFrom(this MutableSpan<int> data, ref Vector4 v)
{
data[0] = (int)v.X;
data[1] = (int)v.Y;
data[2] = (int)v.Z;
data[3] = (int)v.W;
}
/// <summary>
/// Converts all int values of src to float
/// </summary>
/// <param name="src">Source</param>
/// <returns>A new <see cref="MutableSpan{T}"/> with float values</returns>
public static MutableSpan<float> ConvertToFloat32MutableSpan(this MutableSpan<int> src)
{
MutableSpan<float> result = new MutableSpan<float>(src.TotalCount);
for (int i = 0; i < src.TotalCount; i++)
{
result[i] = (float)src[i];
}
return result;
}
/// <summary>
/// Converts all float values of src to int
/// </summary>
/// <param name="src">Source</param>
/// <returns>A new <see cref="MutableSpan{T}"/> with float values</returns>
public static MutableSpan<int> ConvertToInt32MutableSpan(this MutableSpan<float> src)
{
MutableSpan<int> result = new MutableSpan<int>(src.TotalCount);
for (int i = 0; i < src.TotalCount; i++)
{
result[i] = (int)src[i];
}
return result;
}
/// <summary>
/// Add a scalar to all values of src
/// </summary>
/// <param name="src">The source</param>
/// <param name="scalar">The scalar value to add</param>
/// <returns>A new instance of <see cref="MutableSpan{T}"/></returns>
public static MutableSpan<float> AddScalarToAllValues(this MutableSpan<float> src, float scalar)
{
MutableSpan<float> result = new MutableSpan<float>(src.TotalCount);
for (int i = 0; i < src.TotalCount; i++)
{
result[i] = src[i] + scalar;
}
return result;
}
/// <summary>
/// Add a scalar to all values of src
/// </summary>
/// <param name="src">The source</param>
/// <param name="scalar">The scalar value to add</param>
/// <returns>A new instance of <see cref="MutableSpan{T}"/></returns>
public static MutableSpan<int> AddScalarToAllValues(this MutableSpan<int> src, int scalar)
{
MutableSpan<int> result = new MutableSpan<int>(src.TotalCount);
for (int i = 0; i < src.TotalCount; i++)
{
result[i] = src[i] + scalar;
}
return result;
}
/// <summary>
/// Copy all values in src to a new <see cref="MutableSpan{T}"/> instance
/// </summary>
/// <typeparam name="T">Element type</typeparam>
/// <param name="src">The source</param>
/// <returns>A new instance of <see cref="MutableSpan{T}"/></returns>
public static MutableSpan<T> Copy<T>(this MutableSpan<T> src)
{
MutableSpan<T> result = new MutableSpan<T>(src.TotalCount);
for (int i = 0; i < src.TotalCount; i++)
{
result[i] = src[i];
}
return result;
}
}
}

2
src/ImageSharp/Image/PixelAccessor{TColor}.cs

@ -187,7 +187,6 @@ namespace ImageSharp
int height = Math.Min(area.Height, this.Height - targetY);
this.CheckDimensions(width, height);
switch (area.ComponentOrder)
{
case ComponentOrder.ZYX:
@ -222,7 +221,6 @@ namespace ImageSharp
int height = Math.Min(area.Height, this.Height - sourceY);
this.CheckDimensions(width, height);
switch (area.ComponentOrder)
{
case ComponentOrder.ZYX:

144
src/ImageSharp/Image/PixelArea{TColor}.cs

@ -2,10 +2,10 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
@ -18,6 +18,11 @@ namespace ImageSharp
public sealed unsafe class PixelArea<TColor> : IDisposable
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
/// <summary>
/// True if <see cref="Bytes"/> was rented from <see cref="BytesPool"/> by the constructor
/// </summary>
private readonly bool isBufferRented;
/// <summary>
/// Provides a way to access the pixels from unmanaged memory.
/// </summary>
@ -85,8 +90,9 @@ namespace ImageSharp
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="componentOrder">The component order.</param>
public PixelArea(int width, int height, ComponentOrder componentOrder)
: this(width, height, componentOrder, 0)
/// <param name="usePool">True if the buffer should be rented from ArrayPool</param>
public PixelArea(int width, int height, ComponentOrder componentOrder, bool usePool = false)
: this(width, height, componentOrder, 0, usePool)
{
}
@ -95,8 +101,9 @@ namespace ImageSharp
/// </summary>
/// <param name="width">The width.</param>
/// <param name="componentOrder">The component order.</param>
public PixelArea(int width, ComponentOrder componentOrder)
: this(width, 1, componentOrder, 0)
/// <param name="usePool">True if the buffer should be rented from ArrayPool</param>
public PixelArea(int width, ComponentOrder componentOrder, bool usePool = false)
: this(width, 1, componentOrder, 0, usePool)
{
}
@ -106,8 +113,9 @@ namespace ImageSharp
/// <param name="width">The width. </param>
/// <param name="componentOrder">The component order.</param>
/// <param name="padding">The number of bytes to pad each row.</param>
public PixelArea(int width, ComponentOrder componentOrder, int padding)
: this(width, 1, componentOrder, padding)
/// <param name="usePool">True if the buffer should be rented from ArrayPool</param>
public PixelArea(int width, ComponentOrder componentOrder, int padding, bool usePool = false)
: this(width, 1, componentOrder, padding, usePool)
{
}
@ -118,13 +126,27 @@ namespace ImageSharp
/// <param name="height">The height.</param>
/// <param name="componentOrder">The component order.</param>
/// <param name="padding">The number of bytes to pad each row.</param>
public PixelArea(int width, int height, ComponentOrder componentOrder, int padding)
/// <param name="usePool">True if the buffer should be rented from ArrayPool</param>
public PixelArea(int width, int height, ComponentOrder componentOrder, int padding, bool usePool = false)
{
this.Width = width;
this.Height = height;
this.ComponentOrder = componentOrder;
this.RowByteCount = (width * GetComponentCount(componentOrder)) + padding;
this.Bytes = new byte[this.RowByteCount * height];
int bufferSize = this.RowByteCount * height;
if (usePool)
{
this.Bytes = BytesPool.Rent(bufferSize);
this.isBufferRented = true;
Array.Clear(this.Bytes, 0, bufferSize);
}
else
{
this.Bytes = new byte[bufferSize];
}
this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned);
// TODO: Why is Resharper warning us about an impure method call?
@ -137,7 +159,7 @@ namespace ImageSharp
/// </summary>
~PixelArea()
{
this.Dispose();
this.Dispose(false);
}
/// <summary>
@ -145,20 +167,30 @@ namespace ImageSharp
/// </summary>
public byte[] Bytes { get; }
/// <summary>
/// Gets the component order.
/// </summary>
public ComponentOrder ComponentOrder { get; }
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.dataPointer;
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the data pointer.
/// </summary>
public byte* PixelBase { get; private set; }
/// <summary>
/// Gets the component order.
/// Gets number of bytes in a row.
/// </summary>
public ComponentOrder ComponentOrder { get; }
public int RowByteCount { get; }
/// <summary>
/// Gets the width.
@ -166,14 +198,18 @@ namespace ImageSharp
public int Width { get; }
/// <summary>
/// Gets the height.
/// Gets the pool used to rent <see cref="Bytes"/>, when it's not coming from an external source
/// </summary>
public int Height { get; }
// ReSharper disable once StaticMemberInGenericType
private static ArrayPool<byte> BytesPool => ArrayPool<byte>.Shared;
/// <summary>
/// Gets number of bytes in a row.
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public int RowByteCount { get; }
public void Dispose()
{
this.Dispose(true);
}
/// <summary>
/// Reads the stream to the area.
@ -193,45 +229,12 @@ namespace ImageSharp
stream.Write(this.Bytes, 0, this.Bytes.Length);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (this.isDisposed)
{
return;
}
if (this.PixelBase == null)
{
return;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
}
this.dataPointer = IntPtr.Zero;
this.PixelBase = null;
this.isDisposed = true;
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
/// <summary>
/// Resets the bytes of the array to it's initial value.
/// </summary>
internal void Reset()
{
Unsafe.InitBlock(this.PixelBase, 0, (uint)this.Bytes.Length);
Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowByteCount * this.Height));
}
/// <summary>
@ -265,8 +268,45 @@ namespace ImageSharp
int requiredLength = (width * GetComponentCount(componentOrder)) * height;
if (bytes.Length != requiredLength)
{
throw new ArgumentOutOfRangeException(nameof(bytes), $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}.");
throw new ArgumentOutOfRangeException(
nameof(bytes),
$"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}.");
}
}
private void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (this.PixelBase == null)
{
return;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
}
if (disposing && this.isBufferRented)
{
BytesPool.Return(this.Bytes);
}
this.dataPointer = IntPtr.Zero;
this.PixelBase = null;
this.isDisposed = true;
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
}
}
}

3
src/ImageSharp/ImageSharp.xproj

@ -18,5 +18,8 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

2
src/ImageSharp/Numerics/RectangleF.cs

@ -249,7 +249,7 @@ namespace ImageSharp
/// </returns>
public static RectangleF Outset(RectangleF region, float width)
{
var dblWidth = width * 2;
float dblWidth = width * 2;
return new RectangleF(region.X - width, region.Y - width, region.Width + dblWidth, region.Height + dblWidth);
}

20
src/ImageSharp/PixelAccessor.cs

@ -109,6 +109,26 @@ namespace ImageSharp
}
}
/// <inheritdoc />
protected override unsafe void CopyToXYZ(PixelArea<Color> area, int sourceY, int sourceX, int width, int height)
{
for (int y = 0; y < height; y++)
{
byte* source = this.GetRowPointer(sourceX, sourceY + y);
byte* destination = area.PixelBase + (y * area.RowByteCount);
for (int x = 0; x < width; x++)
{
*destination = *(source + 0);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 2);
source += 4;
destination += 3;
}
}
}
/// <inheritdoc />
protected override void CopyToZYXW(PixelArea<Color> area, int sourceY, int sourceX, int width, int height)
{

1
src/ImageSharp/Properties/AssemblyInfo.cs

@ -37,3 +37,4 @@ using System.Runtime.CompilerServices;
// Ensure the internals can be tested.
[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")]
[assembly: InternalsVisibleTo("ImageSharp.Tests")]
[assembly: InternalsVisibleTo("ImageSharp.Tests46")]

96
tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs

@ -1,106 +1,38 @@
// <copyright file="DecodeJpegMultiple.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Collections.Generic;
namespace ImageSharp.Benchmarks.Image
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Image = ImageSharp.Image;
using ImageSharpSize = ImageSharp.Size;
public class DecodeJpegMultiple
public class DecodeJpegMultiple : MultiImageBenchmarkBase
{
private const string Folder = "../ImageSharp.Tests/TestImages/Formats/Jpg/";
private Dictionary<string, byte[]> fileNamesToBytes;
public enum JpegTestingMode
protected override IEnumerable<string> InputImageSubfolders => new[]
{
All,
SmallImagesOnly,
LargeImagesOnly,
"Formats/Jpg/"
};
CalliphoraOnly,
}
[Params(JpegTestingMode.All, JpegTestingMode.SmallImagesOnly, JpegTestingMode.LargeImagesOnly,
JpegTestingMode.CalliphoraOnly)]
public JpegTestingMode Mode { get; set; }
private IEnumerable<KeyValuePair<string, byte[]>> RequestedImages
{
get
{
int thresholdInBytes = 100000;
switch (this.Mode)
{
case JpegTestingMode.All:
return this.fileNamesToBytes;
case JpegTestingMode.SmallImagesOnly:
return this.fileNamesToBytes.Where(kv => kv.Value.Length < thresholdInBytes);
case JpegTestingMode.LargeImagesOnly:
return this.fileNamesToBytes.Where(kv => kv.Value.Length >= thresholdInBytes);
case JpegTestingMode.CalliphoraOnly:
return new[] { this.fileNamesToBytes.First(kv => kv.Key.ToLower().Contains("calliphora")) };
default:
throw new ArgumentOutOfRangeException();
}
}
}
protected override IEnumerable<string> FileFilters => new[] { "*.jpg" };
[Benchmark(Description = "DecodeJpegMultiple - ImageSharp")]
public ImageSharpSize JpegImageSharp()
public void DecodeJpegImageSharp()
{
ImageSharpSize lastSize = new ImageSharpSize();
foreach (var kv in this.RequestedImages)
{
using (MemoryStream memoryStream = new MemoryStream(kv.Value))
{
Image image = new Image(memoryStream);
lastSize = new ImageSharpSize(image.Width, image.Height);
}
}
return lastSize;
this.ForEachStream(
ms => new ImageSharp.Image(ms)
);
}
[Benchmark(Baseline = true, Description = "DecodeJpegMultiple - System.Drawing")]
public Size JpegSystemDrawing()
public void DecodeJpegSystemDrawing()
{
Size lastSize = new Size();
foreach (var kv in this.RequestedImages)
{
using (MemoryStream memoryStream = new MemoryStream(kv.Value))
{
using (System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream))
{
lastSize = image.Size;
}
}
}
return lastSize;
this.ForEachStream(
System.Drawing.Image.FromStream
);
}
[Setup]
public void ReadImages()
{
if (this.fileNamesToBytes != null) return;
var allFiles = Directory.EnumerateFiles(Folder, "*.jpg", SearchOption.AllDirectories).ToArray();
this.fileNamesToBytes = allFiles.ToDictionary(fn => fn, File.ReadAllBytes);
}
}
}

50
tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs

@ -0,0 +1,50 @@
namespace ImageSharp.Benchmarks.Image
{
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Jobs;
using BenchmarkDotNet.Engines;
using ImageSharp.Formats;
public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{
protected override IEnumerable<string> InputImageSubfolders => new[]
{
"Formats/Bmp/",
"Formats/Jpg/baseline"
};
protected override IEnumerable<string> FileFilters => new[] { "*.bmp", "*.jpg" };
[Benchmark(Description = "EncodeJpegMultiple - ImageSharp")]
public void EncodeJpegImageSharp()
{
this.ForEachImageSharpImage(
img =>
{
MemoryStream ms = new MemoryStream();
img.Save(ms, new JpegEncoder());
return ms;
});
}
[Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")]
public void EncodeJpegSystemDrawing()
{
this.ForEachSystemDrawingImage(
img =>
{
MemoryStream ms = new MemoryStream();
img.Save(ms, ImageFormat.Jpeg);
return ms;
});
}
}
}

185
tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs

@ -0,0 +1,185 @@
namespace ImageSharp.Benchmarks.Image
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Image = ImageSharp.Image;
public abstract class MultiImageBenchmarkBase
{
protected Dictionary<string, byte[]> FileNamesToBytes = new Dictionary<string, byte[]>();
protected Dictionary<string, Image> FileNamesToImageSharpImages = new Dictionary<string, Image>();
protected Dictionary<string, System.Drawing.Bitmap> FileNamesToSystemDrawingImages = new Dictionary<string, System.Drawing.Bitmap>();
public enum TestingMode
{
All,
SmallImagesOnly,
LargeImagesOnly
}
[Params(TestingMode.All, TestingMode.SmallImagesOnly, TestingMode.LargeImagesOnly)]
public TestingMode Mode { get; set; }
protected virtual string BaseFolder => "../ImageSharp.Tests/TestImages/";
protected abstract IEnumerable<string> FileFilters { get; }
protected IEnumerable<string> FilterWords => new string[] { };
protected virtual IEnumerable<string> Folders => this.InputImageSubfolders.Select(f => Path.Combine(this.BaseFolder, f));
protected virtual int LargeImageThresholdInBytes => 100000;
protected IEnumerable<KeyValuePair<string, T>> EnumeratePairsByBenchmarkSettings<T>(
Dictionary<string, T> input,
Predicate<T> checkIfSmall)
{
switch (this.Mode)
{
case TestingMode.All:
return input;
case TestingMode.SmallImagesOnly:
return input.Where(kv => checkIfSmall(kv.Value));
case TestingMode.LargeImagesOnly:
return input.Where(kv => !checkIfSmall(kv.Value));
default:
throw new ArgumentOutOfRangeException();
}
}
protected IEnumerable<KeyValuePair<string, byte[]>> FileNames2Bytes
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToBytes,
arr => arr.Length < this.LargeImageThresholdInBytes);
protected abstract IEnumerable<string> InputImageSubfolders { get; }
[Setup]
public void ReadImages()
{
//Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated);
this.ReadImagesImpl();
}
protected virtual void ReadImagesImpl()
{
foreach (string folder in this.Folders)
{
var allFiles =
this.FileFilters.SelectMany(
f =>
Directory.EnumerateFiles(folder, f, SearchOption.AllDirectories)
.Where(fn => !this.FilterWords.Any(w => fn.ToLower().Contains(w)))).ToArray();
foreach (var fn in allFiles)
{
this.FileNamesToBytes[fn] = File.ReadAllBytes(fn);
}
}
}
protected void ForEachStream(Func<MemoryStream, object> operation)
{
foreach (var kv in this.FileNames2Bytes)
{
using (MemoryStream memoryStream = new MemoryStream(kv.Value))
{
try
{
var obj = operation(memoryStream);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
}
}
}
public abstract class WithImagesPreloaded : MultiImageBenchmarkBase
{
protected override void ReadImagesImpl()
{
base.ReadImagesImpl();
foreach (var kv in this.FileNamesToBytes)
{
byte[] bytes = kv.Value;
string fn = kv.Key;
using (var ms1 = new MemoryStream(bytes))
{
this.FileNamesToImageSharpImages[fn] = new Image(ms1);
}
this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes));
}
}
protected IEnumerable<KeyValuePair<string, ImageSharp.Image>> FileNames2ImageSharpImages
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToImageSharpImages,
img => img.Width * img.Height < this.LargeImageThresholdInPixels);
protected IEnumerable<KeyValuePair<string, System.Drawing.Bitmap>> FileNames2SystemDrawingImages
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToSystemDrawingImages,
img => img.Width * img.Height < this.LargeImageThresholdInPixels);
protected virtual int LargeImageThresholdInPixels => 700000;
protected void ForEachImageSharpImage(Func<Image, object> operation)
{
foreach (var kv in this.FileNames2ImageSharpImages)
{
try
{
var obj = operation(kv.Value);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
}
}
protected void ForEachSystemDrawingImage(Func<System.Drawing.Bitmap, object> operation)
{
foreach (var kv in this.FileNames2SystemDrawingImages)
{
try
{
var obj = operation(kv.Value);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
}
}
}
}
}

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

@ -1,20 +1,19 @@
// Uncomment this to turn unit tests into benchmarks:
//#define BENCHMARKING
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using ImageSharp.Formats;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
namespace ImageSharp.Tests
{
using System.Diagnostics;
using System.Numerics;
using ImageSharp.Formats;
using ImageSharp.Formats.Jpg;
using Xunit;
using Xunit.Abstractions;
public class Block8x8FTests : UtilityTestClassBase
{
#if BENCHMARKING
@ -23,7 +22,8 @@ namespace ImageSharp.Tests.Formats.Jpg
public const int Times = 1;
#endif
public Block8x8FTests(ITestOutputHelper output) : base(output)
public Block8x8FTests(ITestOutputHelper output)
: base(output)
{
}
@ -31,20 +31,23 @@ namespace ImageSharp.Tests.Formats.Jpg
public void Indexer()
{
float sum = 0;
this.Measure(Times, () =>
{
Block8x8F block = new Block8x8F();
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
block[i] = i;
}
sum = 0;
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
sum += block[i];
}
});
this.Measure(
Times,
() =>
{
Block8x8F block = new Block8x8F();
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
block[i] = i;
}
sum = 0;
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
sum += block[i];
}
});
Assert.Equal(sum, 64f * 63f * 0.5f);
}
@ -52,21 +55,24 @@ namespace ImageSharp.Tests.Formats.Jpg
public unsafe void Indexer_GetScalarAt_SetScalarAt()
{
float sum = 0;
Measure(Times, () =>
{
Block8x8F block = new Block8x8F();
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
Block8x8F.SetScalarAt(&block, i, i);
}
sum = 0;
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
sum += Block8x8F.GetScalarAt(&block, i);
}
});
Assert.Equal(sum, 64f*63f*0.5f);
this.Measure(
Times,
() =>
{
Block8x8F block = new Block8x8F();
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
Block8x8F.SetScalarAt(&block, i, i);
}
sum = 0;
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
sum += Block8x8F.GetScalarAt(&block, i);
}
});
Assert.Equal(sum, 64f * 63f * 0.5f);
}
[Fact]
@ -74,22 +80,24 @@ namespace ImageSharp.Tests.Formats.Jpg
{
float sum = 0;
Measure(Times, () =>
{
//Block8x8F block = new Block8x8F();
float[] block = new float[64];
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
block[i] = i;
}
sum = 0;
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
sum += block[i];
}
});
Assert.Equal(sum, 64f*63f*0.5f);
this.Measure(
Times,
() =>
{
// Block8x8F block = new Block8x8F();
float[] block = new float[64];
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
block[i] = i;
}
sum = 0;
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
sum += block[i];
}
});
Assert.Equal(sum, 64f * 63f * 0.5f);
}
[Fact]
@ -102,15 +110,19 @@ namespace ImageSharp.Tests.Formats.Jpg
{
data[i] = i;
}
Measure(Times, () =>
{
Block8x8F b = new Block8x8F();
b.LoadFrom(data);
b.CopyTo(mirror);
});
this.Measure(
Times,
() =>
{
Block8x8F b = new Block8x8F();
b.LoadFrom(data);
b.CopyTo(mirror);
});
Assert.Equal(data, mirror);
//PrintLinearData((MutableSpan<float>)mirror);
// PrintLinearData((MutableSpan<float>)mirror);
}
[Fact]
@ -123,15 +135,19 @@ namespace ImageSharp.Tests.Formats.Jpg
{
data[i] = i;
}
Measure(Times, () =>
{
Block8x8F b = new Block8x8F();
Block8x8F.LoadFrom(&b, data);
Block8x8F.CopyTo(&b, mirror);
});
this.Measure(
Times,
() =>
{
Block8x8F b = new Block8x8F();
Block8x8F.LoadFrom(&b, data);
Block8x8F.CopyTo(&b, mirror);
});
Assert.Equal(data, mirror);
//PrintLinearData((MutableSpan<float>)mirror);
// PrintLinearData((MutableSpan<float>)mirror);
}
[Fact]
@ -144,19 +160,21 @@ namespace ImageSharp.Tests.Formats.Jpg
{
data[i] = i;
}
Measure(Times, () =>
{
Block8x8F v = new Block8x8F();
v.LoadFrom(data);
v.CopyTo(mirror);
});
this.Measure(
Times,
() =>
{
Block8x8F v = new Block8x8F();
v.LoadFrom(data);
v.CopyTo(mirror);
});
Assert.Equal(data, mirror);
//PrintLinearData((MutableSpan<int>)mirror);
// PrintLinearData((MutableSpan<int>)mirror);
}
[Fact]
public void TransposeInto()
{
@ -174,8 +192,6 @@ namespace ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual);
}
private class BufferHolder
{
@ -189,7 +205,7 @@ namespace ImageSharp.Tests.Formats.Jpg
source.Buffer.LoadFrom(Create8x8FloatData());
BufferHolder dest = new BufferHolder();
Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ...");
this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ...");
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < Times; i++)
@ -198,10 +214,9 @@ namespace ImageSharp.Tests.Formats.Jpg
}
sw.Stop();
Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
}
[Fact]
public void iDCT2D8x4_LeftPart()
{
@ -215,14 +230,14 @@ namespace ImageSharp.Tests.Formats.Jpg
Block8x8F dest = new Block8x8F();
source.IDCT8x4_LeftPart(ref dest);
DCT.IDCT8x4_LeftPart(ref source, ref dest);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
Print8x8Data(expectedDestArray);
Output.WriteLine("**************");
Print8x8Data(actualDestArray);
this.Print8x8Data(expectedDestArray);
this.Output.WriteLine("**************");
this.Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray, actualDestArray);
}
@ -240,65 +255,49 @@ namespace ImageSharp.Tests.Formats.Jpg
Block8x8F dest = new Block8x8F();
source.IDCT8x4_RightPart(ref dest);
DCT.IDCT8x4_RightPart(ref source, ref dest);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
Print8x8Data(expectedDestArray);
Output.WriteLine("**************");
Print8x8Data(actualDestArray);
this.Print8x8Data(expectedDestArray);
this.Output.WriteLine("**************");
this.Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray.Data, actualDestArray);
}
private struct ApproximateFloatComparer : IEqualityComparer<float>
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void TransformIDCT(int seed)
{
private const float Eps = 0.0001f;
public bool Equals(float x, float y)
{
float d = x - y;
return d > -Eps && d < Eps;
}
public int GetHashCode(float obj)
{
throw new InvalidOperationException();
}
}
[Fact]
public void IDCTInto()
{
float[] sourceArray = Create8x8FloatData();
var sourceArray = Create8x8RandomFloatData(-200, 200, seed);
float[] expectedDestArray = new float[64];
float[] tempArray = new float[64];
ReferenceImplementations.iDCT2D_llm(sourceArray, expectedDestArray, tempArray);
//ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray);
// ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray);
Block8x8F source = new Block8x8F();
source.LoadFrom(sourceArray);
Block8x8F dest = new Block8x8F();
Block8x8F tempBuffer = new Block8x8F();
source.TransformIDCTInto(ref dest, ref tempBuffer);
DCT.TransformIDCT(ref source, ref dest, ref tempBuffer);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
Print8x8Data(expectedDestArray);
Output.WriteLine("**************");
Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer());
Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer());
this.Print8x8Data(expectedDestArray);
this.Output.WriteLine("**************");
this.Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f));
Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f));
}
[Fact]
public unsafe void CopyColorsTo()
{
@ -309,24 +308,23 @@ namespace ImageSharp.Tests.Formats.Jpg
int stride = 256;
int height = 42;
int offset = height*10 + 20;
int offset = height * 10 + 20;
byte[] colorsExpected = new byte[stride*height];
byte[] colorsActual = new byte[stride*height];
byte[] colorsExpected = new byte[stride * height];
byte[] colorsActual = new byte[stride * height];
Block8x8F temp = new Block8x8F();
ReferenceImplementations.CopyColorsTo(ref block, new MutableSpan<byte>(colorsExpected, offset), stride);
block.CopyColorsTo(new MutableSpan<byte>(colorsActual, offset), stride, &temp);
//Output.WriteLine("******* EXPECTED: *********");
//PrintLinearData(colorsExpected);
//Output.WriteLine("******** ACTUAL: **********");
// Output.WriteLine("******* EXPECTED: *********");
// PrintLinearData(colorsExpected);
// Output.WriteLine("******** ACTUAL: **********");
Assert.Equal(colorsExpected, colorsActual);
}
private static float[] Create8x8ColorCropTestData()
{
float[] result = new float[64];
@ -337,6 +335,7 @@ namespace ImageSharp.Tests.Formats.Jpg
result[i * 8 + j] = -300 + i * 100 + j * 10;
}
}
return result;
}
@ -346,22 +345,88 @@ namespace ImageSharp.Tests.Formats.Jpg
Block8x8F block = new Block8x8F();
var input = Create8x8ColorCropTestData();
block.LoadFrom(input);
Output.WriteLine("Input:");
PrintLinearData(input);
this.Output.WriteLine("Input:");
this.PrintLinearData(input);
Block8x8F dest = new Block8x8F();
block.TransformByteConvetibleColorValuesInto(ref dest);
float[] array = new float[64];
dest.CopyTo(array);
Output.WriteLine("Result:");
PrintLinearData(array);
this.Output.WriteLine("Result:");
this.PrintLinearData(array);
foreach (float val in array)
{
Assert.InRange(val, 0, 255);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void FDCT8x4_LeftPart(int seed)
{
var src = Create8x8RandomFloatData(-200, 200, seed);
var srcBlock = new Block8x8F();
srcBlock.LoadFrom(src);
var destBlock = new Block8x8F();
var expectedDest = new MutableSpan<float>(64);
ReferenceImplementations.fDCT2D8x4_32f(src, expectedDest);
DCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock);
var actualDest = new MutableSpan<float>(64);
destBlock.CopyTo(actualDest);
Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f));
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void FDCT8x4_RightPart(int seed)
{
var src = Create8x8RandomFloatData(-200, 200, seed);
var srcBlock = new Block8x8F();
srcBlock.LoadFrom(src);
var destBlock = new Block8x8F();
var expectedDest = new MutableSpan<float>(64);
ReferenceImplementations.fDCT2D8x4_32f(src.Slice(4), expectedDest.Slice(4));
DCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock);
var actualDest = new MutableSpan<float>(64);
destBlock.CopyTo(actualDest);
Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f));
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TransformFDCT(int seed)
{
var src = Create8x8RandomFloatData(-200, 200, seed);
var srcBlock = new Block8x8F();
srcBlock.LoadFrom(src);
var destBlock = new Block8x8F();
var expectedDest = new MutableSpan<float>(64);
var temp1 = new MutableSpan<float>(64);
var temp2 = new Block8x8F();
ReferenceImplementations.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true);
DCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false);
var actualDest = new MutableSpan<float>(64);
destBlock.CopyTo(actualDest);
Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f));
}
}
}

166
tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs

@ -5,20 +5,21 @@ using System.Linq;
using ImageSharp.Formats;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests
{
public class JpegTests
{
public const string TestOutputDirectory = "TestOutput/Jpeg";
using System.Numerics;
private ITestOutputHelper Output { get; }
using ImageSharp.Formats.Jpg;
public class JpegTests : MeasureFixture
{
public JpegTests(ITestOutputHelper output)
: base(output)
{
this.Output = output;
}
public static IEnumerable<string> AllJpegFiles => TestImages.Jpeg.All;
[Theory]
@ -56,5 +57,158 @@ namespace ImageSharp.Tests
image.Save(outputStream, encoder);
}
}
private const int BenchmarkExecTimes = 30;
public static readonly string[] EncoderBenchmarkFiles =
{
TestImages.Bmp.Car, TestImages.Bmp.NegHeight,
TestImages.Bmp.F, TestImages.Png.Splash,
TestImages.Jpeg.Jpeg420, TestImages.Jpeg.Calliphora,
TestImages.Jpeg.Cmyk
};
private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; //PixelTypes.Color | PixelTypes.Argb;
//[Theory] // Benchmark, enable manually
[InlineData(TestImages.Jpeg.Cmyk)]
[InlineData(TestImages.Jpeg.Ycck)]
[InlineData(TestImages.Jpeg.Calliphora)]
[InlineData(TestImages.Jpeg.Jpeg400)]
[InlineData(TestImages.Jpeg.Jpeg420)]
[InlineData(TestImages.Jpeg.Jpeg444)]
public void Benchmark_JpegDecoder(string fileName)
{
string path = TestFile.GetPath(fileName);
byte[] bytes = File.ReadAllBytes(path);
this.Measure(
40,
() =>
{
Image img = new Image(bytes);
},
$"Decode {fileName}");
}
//[Theory] // Benchmark, enable manually
[WithFileCollection(nameof(EncoderBenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)]
[WithFileCollection(nameof(EncoderBenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio444, 75)]
public void Benchmark_JpegEncoder<TColor>(TestImageProvider<TColor> provider, JpegSubsample subSample, int quality)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
var image = provider.GetImage();
using (var outputStream = new MemoryStream())
{
var encoder = new JpegEncoder()
{
Subsample = subSample,
Quality = quality
};
for (int i = 0; i < BenchmarkExecTimes; i++)
{
image.Save(outputStream, encoder);
outputStream.Seek(0, SeekOrigin.Begin);
}
}
}
public static Image<TColor> CreateTestImage<TColor>(GenericFactory<TColor> factory)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
Image<TColor> image = factory.CreateImage(10, 10);
using (var pixels = image.Lock())
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
Vector4 v = new Vector4(i/10f, j/10f, 0, 1);
TColor color = default(TColor);
color.PackFromVector4(v);
pixels[i, j] = color;
}
}
}
return image;
}
[Theory]
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)]
public void CopyStretchedRGBTo_FromOrigo<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
var src = provider.GetImage();
PixelArea<TColor> area = new PixelArea<TColor>(8, 8, ComponentOrder.XYZ);
var dest = provider.Factory.CreateImage(8, 8);
using (var s = src.Lock())
{
using (var d = dest.Lock())
{
s.CopyRGBBytesStretchedTo(area, 0, 0);
d.CopyFrom(area, 0, 0);
Assert.Equal(s[0, 0], d[0, 0]);
Assert.Equal(s[7, 0], d[7, 0]);
Assert.Equal(s[0, 7], d[0, 7]);
Assert.Equal(s[7, 7], d[7, 7]);
}
}
}
[Theory]
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)]
public void CopyStretchedRGBTo_WithOffset<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
var src = provider.GetImage();
PixelArea<TColor> area = new PixelArea<TColor>(8, 8, ComponentOrder.XYZ);
var dest = provider.Factory.CreateImage(8, 8);
using (var s = src.Lock())
{
using (var d = dest.Lock())
{
s.CopyRGBBytesStretchedTo(area, 7, 6);
d.CopyFrom(area, 0, 0);
Assert.Equal(s[6, 7], d[0, 0]);
Assert.Equal(s[6, 8], d[0, 1]);
Assert.Equal(s[7, 8], d[1, 1]);
Assert.Equal(s[6, 9], d[0, 2]);
Assert.Equal(s[6, 9], d[0, 3]);
Assert.Equal(s[6, 9], d[0, 7]);
Assert.Equal(s[7, 9], d[1, 2]);
Assert.Equal(s[7, 9], d[1, 3]);
Assert.Equal(s[7, 9], d[1, 7]);
Assert.Equal(s[9, 9], d[3, 2]);
Assert.Equal(s[9, 9], d[3, 3]);
Assert.Equal(s[9, 9], d[3, 7]);
Assert.Equal(s[9, 7], d[3, 0]);
Assert.Equal(s[9, 7], d[4, 0]);
Assert.Equal(s[9, 7], d[7, 0]);
Assert.Equal(s[9, 9], d[3, 2]);
Assert.Equal(s[9, 9], d[4, 2]);
Assert.Equal(s[9, 9], d[7, 2]);
Assert.Equal(s[9, 9], d[4, 3]);
Assert.Equal(s[9, 9], d[7, 7]);
}
}
}
}
}

542
tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs

@ -1,15 +1,17 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
namespace ImageSharp.Tests
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp.Formats;
using ImageSharp.Formats.Jpg;
/// <summary>
/// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests
/// DCT code Ported from https://github.com/norishigefukushima/dct_simd
/// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd
/// </summary>
internal static class ReferenceImplementations
{
@ -46,6 +48,310 @@ namespace ImageSharp.Tests.Formats.Jpg
}
}
public static class IntegerReferenceDCT
{
private const int fix_0_298631336 = 2446;
private const int fix_0_390180644 = 3196;
private const int fix_0_541196100 = 4433;
private const int fix_0_765366865 = 6270;
private const int fix_0_899976223 = 7373;
private const int fix_1_175875602 = 9633;
private const int fix_1_501321110 = 12299;
private const int fix_1_847759065 = 15137;
private const int fix_1_961570560 = 16069;
private const int fix_2_053119869 = 16819;
private const int fix_2_562915447 = 20995;
private const int fix_3_072711026 = 25172;
/// <summary>
/// The number of bits
/// </summary>
private const int Bits = 13;
/// <summary>
/// The number of bits to shift by on the first pass.
/// </summary>
private const int Pass1Bits = 2;
/// <summary>
/// The value to shift by
/// </summary>
private const int CenterJSample = 128;
/// <summary>
/// Performs a forward DCT on an 8x8 block of coefficients, including a level shift.
/// Leave results scaled up by an overall factor of 8.
/// </summary>
/// <param name="block">The block of coefficients.</param>
public static void TransformFDCTInplace(MutableSpan<int> block)
{
// Pass 1: process rows.
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
int x0 = block[y8];
int x1 = block[y8 + 1];
int x2 = block[y8 + 2];
int x3 = block[y8 + 3];
int x4 = block[y8 + 4];
int x5 = block[y8 + 5];
int x6 = block[y8 + 6];
int x7 = block[y8 + 7];
int tmp0 = x0 + x7;
int tmp1 = x1 + x6;
int tmp2 = x2 + x5;
int tmp3 = x3 + x4;
int tmp10 = tmp0 + tmp3;
int tmp12 = tmp0 - tmp3;
int tmp11 = tmp1 + tmp2;
int tmp13 = tmp1 - tmp2;
tmp0 = x0 - x7;
tmp1 = x1 - x6;
tmp2 = x2 - x5;
tmp3 = x3 - x4;
block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits;
block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits;
int z1 = (tmp12 + tmp13) * fix_0_541196100;
z1 += 1 << (Bits - Pass1Bits - 1);
block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits);
block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits);
tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp0 + tmp2;
tmp13 = tmp1 + tmp3;
z1 = (tmp12 + tmp13) * fix_1_175875602;
z1 += 1 << (Bits - Pass1Bits - 1);
tmp0 = tmp0 * fix_1_501321110;
tmp1 = tmp1 * fix_3_072711026;
tmp2 = tmp2 * fix_2_053119869;
tmp3 = tmp3 * fix_0_298631336;
tmp10 = tmp10 * -fix_0_899976223;
tmp11 = tmp11 * -fix_2_562915447;
tmp12 = tmp12 * -fix_0_390180644;
tmp13 = tmp13 * -fix_1_961570560;
tmp12 += z1;
tmp13 += z1;
block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits);
block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits);
block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits);
block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits);
}
// Pass 2: process columns.
// We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8.
for (int x = 0; x < 8; x++)
{
int tmp0 = block[x] + block[56 + x];
int tmp1 = block[8 + x] + block[48 + x];
int tmp2 = block[16 + x] + block[40 + x];
int tmp3 = block[24 + x] + block[32 + x];
int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1));
int tmp12 = tmp0 - tmp3;
int tmp11 = tmp1 + tmp2;
int tmp13 = tmp1 - tmp2;
tmp0 = block[x] - block[56 + x];
tmp1 = block[8 + x] - block[48 + x];
tmp2 = block[16 + x] - block[40 + x];
tmp3 = block[24 + x] - block[32 + x];
block[x] = (tmp10 + tmp11) >> Pass1Bits;
block[32 + x] = (tmp10 - tmp11) >> Pass1Bits;
int z1 = (tmp12 + tmp13) * fix_0_541196100;
z1 += 1 << (Bits + Pass1Bits - 1);
block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits);
block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits);
tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp0 + tmp2;
tmp13 = tmp1 + tmp3;
z1 = (tmp12 + tmp13) * fix_1_175875602;
z1 += 1 << (Bits + Pass1Bits - 1);
tmp0 = tmp0 * fix_1_501321110;
tmp1 = tmp1 * fix_3_072711026;
tmp2 = tmp2 * fix_2_053119869;
tmp3 = tmp3 * fix_0_298631336;
tmp10 = tmp10 * -fix_0_899976223;
tmp11 = tmp11 * -fix_2_562915447;
tmp12 = tmp12 * -fix_0_390180644;
tmp13 = tmp13 * -fix_1_961570560;
tmp12 += z1;
tmp13 += z1;
block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits);
block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits);
block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits);
block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits);
}
}
private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16)
private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16)
private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16)
private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16)
private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16)
private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16)
private const int w1pw7 = w1 + w7;
private const int w1mw7 = w1 - w7;
private const int w2pw6 = w2 + w6;
private const int w2mw6 = w2 - w6;
private const int w3pw5 = w3 + w5;
private const int w3mw5 = w3 - w5;
private const int r2 = 181; // 256/sqrt(2)
/// <summary>
/// Performs a 2-D Inverse Discrete Cosine Transformation.
/// <para>
/// The input coefficients should already have been multiplied by the
/// appropriate quantization table. We use fixed-point computation, with the
/// number of bits for the fractional component varying over the intermediate
/// stages.
/// </para>
/// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the
/// discrete W transform and for the discrete Fourier transform", IEEE Trans. on
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
/// </summary>
/// <param name="src">The source block of coefficients</param>
public static void TransformIDCTInplace(MutableSpan<int> src)
{
// Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
// If all the AC components are zero, then the IDCT is trivial.
if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 &&
src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0)
{
int dc = src[y8 + 0] << 3;
src[y8 + 0] = dc;
src[y8 + 1] = dc;
src[y8 + 2] = dc;
src[y8 + 3] = dc;
src[y8 + 4] = dc;
src[y8 + 5] = dc;
src[y8 + 6] = dc;
src[y8 + 7] = dc;
continue;
}
// Prescale.
int x0 = (src[y8 + 0] << 11) + 128;
int x1 = src[y8 + 4] << 11;
int x2 = src[y8 + 6];
int x3 = src[y8 + 2];
int x4 = src[y8 + 1];
int x5 = src[y8 + 7];
int x6 = src[y8 + 5];
int x7 = src[y8 + 3];
// Stage 1.
int x8 = w7 * (x4 + x5);
x4 = x8 + (w1mw7 * x4);
x5 = x8 - (w1pw7 * x5);
x8 = w3 * (x6 + x7);
x6 = x8 - (w3mw5 * x6);
x7 = x8 - (w3pw5 * x7);
// Stage 2.
x8 = x0 + x1;
x0 -= x1;
x1 = w6 * (x3 + x2);
x2 = x1 - (w2pw6 * x2);
x3 = x1 + (w2mw6 * x3);
x1 = x4 + x6;
x4 -= x6;
x6 = x5 + x7;
x5 -= x7;
// Stage 3.
x7 = x8 + x3;
x8 -= x3;
x3 = x0 + x2;
x0 -= x2;
x2 = ((r2 * (x4 + x5)) + 128) >> 8;
x4 = ((r2 * (x4 - x5)) + 128) >> 8;
// Stage 4.
src[y8 + 0] = (x7 + x1) >> 8;
src[y8 + 1] = (x3 + x2) >> 8;
src[y8 + 2] = (x0 + x4) >> 8;
src[y8 + 3] = (x8 + x6) >> 8;
src[y8 + 4] = (x8 - x6) >> 8;
src[y8 + 5] = (x0 - x4) >> 8;
src[y8 + 6] = (x3 - x2) >> 8;
src[y8 + 7] = (x7 - x1) >> 8;
}
// Vertical 1-D IDCT.
for (int x = 0; x < 8; x++)
{
// Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial.
// However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so
// we do not bother to check for the all-zero case.
// Prescale.
int y0 = (src[x] << 8) + 8192;
int y1 = src[32 + x] << 8;
int y2 = src[48 + x];
int y3 = src[16 + x];
int y4 = src[8 + x];
int y5 = src[56 + x];
int y6 = src[40 + x];
int y7 = src[24 + x];
// Stage 1.
int y8 = (w7 * (y4 + y5)) + 4;
y4 = (y8 + (w1mw7 * y4)) >> 3;
y5 = (y8 - (w1pw7 * y5)) >> 3;
y8 = (w3 * (y6 + y7)) + 4;
y6 = (y8 - (w3mw5 * y6)) >> 3;
y7 = (y8 - (w3pw5 * y7)) >> 3;
// Stage 2.
y8 = y0 + y1;
y0 -= y1;
y1 = (w6 * (y3 + y2)) + 4;
y2 = (y1 - (w2pw6 * y2)) >> 3;
y3 = (y1 + (w2mw6 * y3)) >> 3;
y1 = y4 + y6;
y4 -= y6;
y6 = y5 + y7;
y5 -= y7;
// Stage 3.
y7 = y8 + y3;
y8 -= y3;
y3 = y0 + y2;
y0 -= y2;
y2 = ((r2 * (y4 + y5)) + 128) >> 8;
y4 = ((r2 * (y4 - y5)) + 128) >> 8;
// Stage 4.
src[x] = (y7 + y1) >> 14;
src[8 + x] = (y3 + y2) >> 14;
src[16 + x] = (y0 + y4) >> 14;
src[24 + x] = (y8 + y6) >> 14;
src[32 + x] = (y8 - y6) >> 14;
src[40 + x] = (y0 - y4) >> 14;
src[48 + x] = (y3 - y2) >> 14;
src[56 + x] = (y7 - y1) >> 14;
}
}
}
/// <summary>
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200
/// </summary>
@ -56,9 +362,11 @@ namespace ImageSharp.Tests.Formats.Jpg
float a0, a1, a2, a3, b0, b1, b2, b3;
float z0, z1, z2, z3, z4;
float r0 = 1.414214f;
float r1 = 1.387040f;
float r2 = 1.306563f;
float r3 = 1.175876f;
float r4 = 1.000000f;
float r5 = 0.785695f;
float r6 = 0.541196f;
float r7 = 0.275899f;
@ -130,6 +438,145 @@ namespace ImageSharp.Tests.Formats.Jpg
}
}
/// <summary>
/// Original:
/// <see>
/// <cref>https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15</cref>
/// </see>
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void fDCT2D8x4_32f(MutableSpan<float> s, MutableSpan<float> d)
{
Vector4 c0 = _mm_load_ps(s, 0);
Vector4 c1 = _mm_load_ps(s, 56);
Vector4 t0 = (c0 + c1);
Vector4 t7 = (c0 - c1);
c1 = _mm_load_ps(s, 48);
c0 = _mm_load_ps(s, 8);
Vector4 t1 = (c0 + c1);
Vector4 t6 = (c0 - c1);
c1 = _mm_load_ps(s, 40);
c0 = _mm_load_ps(s, 16);
Vector4 t2 = (c0 + c1);
Vector4 t5 = (c0 - c1);
c0 = _mm_load_ps(s, 24);
c1 = _mm_load_ps(s, 32);
Vector4 t3 = (c0 + c1);
Vector4 t4 = (c0 - c1);
/*
c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2;
c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2;
c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2;
c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2;
*/
c0 = (t0 + t3);
Vector4 c3 = (t0 - t3);
c1 = (t1 + t2);
Vector4 c2 = (t1 - t2);
/*
c0 = t0 + t3; c3 = t0 - t3;
c1 = t1 + t2; c2 = t1 - t2;
*/
_mm_store_ps(d, 0, (c0 + c1));
_mm_store_ps(d, 32, (c0 - c1));
/*y[0] = c0 + c1;
y[4] = c0 - c1;*/
Vector4 w0 = new Vector4(0.541196f);
Vector4 w1 = new Vector4(1.306563f);
_mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3)));
_mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2)));
/*
y[2] = c2 * r[6] + c3 * r[2];
y[6] = c3 * r[6] - c2 * r[2];
*/
w0 = new Vector4(1.175876f);
w1 = new Vector4(0.785695f);
c3 = ((w0 * t4) + (w1 * t7));
c0 = ((w0 * t7) - (w1 * t4));
/*
c3 = t4 * r[3] + t7 * r[5];
c0 = t7 * r[3] - t4 * r[5];
*/
w0 = new Vector4(1.387040f);
w1 = new Vector4(0.275899f);
c2 = ((w0 * t5) + (w1 * t6));
c1 = ((w0 * t6) - (w1 * t5));
/*
c2 = t5 * r[1] + t6 * r[7];
c1 = t6 * r[1] - t5 * r[7];
*/
_mm_store_ps(d, 24, (c0 - c2));
_mm_store_ps(d, 40, (c3 - c1));
//y[5] = c3 - c1; y[3] = c0 - c2;
Vector4 invsqrt2 = new Vector4(0.707107f);
c0 = ((c0 + c2) * invsqrt2);
c3 = ((c3 + c1) * invsqrt2);
//c0 = (c0 + c2) * invsqrt2;
//c3 = (c3 + c1) * invsqrt2;
_mm_store_ps(d, 8, (c0 + c3));
_mm_store_ps(d, 56, (c0 - c3));
//y[1] = c0 + c3; y[7] = c0 - c3;
/*for(i = 0;i < 8;i++)
{
y[i] *= invsqrt2h;
}*/
}
public static void fDCT8x8_llm_sse(MutableSpan<float> s, MutableSpan<float> d, MutableSpan<float> temp)
{
Transpose8x8(s, temp);
fDCT2D8x4_32f(temp, d);
fDCT2D8x4_32f(temp.Slice(4), d.Slice(4));
Transpose8x8(d, temp);
fDCT2D8x4_32f(temp, d);
fDCT2D8x4_32f(temp.Slice(4), d.Slice(4));
Vector4 c = new Vector4(0.1250f);
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//0
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//1
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//2
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//3
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//4
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//5
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//6
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//7
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//8
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//9
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//10
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//11
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//12
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//13
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//14
_mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//15
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 _mm_load_ps(MutableSpan<float> src, int offset)
{
@ -329,5 +776,96 @@ namespace ImageSharp.Tests.Formats.Jpg
}
}
}
internal static void fDCT1Dllm_32f(MutableSpan<float> x, MutableSpan<float> y)
{
float t0, t1, t2, t3, t4, t5, t6, t7;
float c0, c1, c2, c3;
float[] r = new float[8];
//for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); }
r[0] = 1.414214f;
r[1] = 1.387040f;
r[2] = 1.306563f;
r[3] = 1.175876f;
r[4] = 1.000000f;
r[5] = 0.785695f;
r[6] = 0.541196f;
r[7] = 0.275899f;
const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2);
const float invsqrt2h = 0.353554f; //invsqrt2*0.5f;
c1 = x[0];
c2 = x[7];
t0 = c1 + c2;
t7 = c1 - c2;
c1 = x[1];
c2 = x[6];
t1 = c1 + c2;
t6 = c1 - c2;
c1 = x[2];
c2 = x[5];
t2 = c1 + c2;
t5 = c1 - c2;
c1 = x[3];
c2 = x[4];
t3 = c1 + c2;
t4 = c1 - c2;
c0 = t0 + t3;
c3 = t0 - t3;
c1 = t1 + t2;
c2 = t1 - t2;
y[0] = c0 + c1;
y[4] = c0 - c1;
y[2] = c2 * r[6] + c3 * r[2];
y[6] = c3 * r[6] - c2 * r[2];
c3 = t4 * r[3] + t7 * r[5];
c0 = t7 * r[3] - t4 * r[5];
c2 = t5 * r[1] + t6 * r[7];
c1 = t6 * r[1] - t5 * r[7];
y[5] = c3 - c1;
y[3] = c0 - c2;
c0 = (c0 + c2) * invsqrt2;
c3 = (c3 + c1) * invsqrt2;
y[1] = c0 + c3;
y[7] = c0 - c3;
}
internal static void fDCT2D_llm(
MutableSpan<float> s,
MutableSpan<float> d,
MutableSpan<float> temp,
bool downscaleBy8 = false,
bool offsetSourceByNeg128 = false)
{
MutableSpan<float> sWorker = offsetSourceByNeg128 ? s.AddScalarToAllValues(-128f) : s;
for (int j = 0; j < 8; j++)
{
fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8));
}
Transpose8x8(temp, d);
for (int j = 0; j < 8; j++)
{
fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8));
}
Transpose8x8(temp, d);
if (downscaleBy8)
{
for (int j = 0; j < 64; j++)
{
d[j] *= 0.125f;
}
}
}
}
}

127
tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs

@ -0,0 +1,127 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
{
using System.Numerics;
using ImageSharp.Formats;
using ImageSharp.Formats.Jpg;
using Xunit;
using Xunit.Abstractions;
public class ReferenceImplementationsTests : UtilityTestClassBase
{
public ReferenceImplementationsTests(ITestOutputHelper output)
: base(output)
{
}
[Theory]
[InlineData(42)]
[InlineData(1)]
[InlineData(2)]
public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed)
{
MutableSpan<int> intData = Create8x8RandomIntData(-200, 200, seed);
MutableSpan<float> floatSrc = intData.ConvertToFloat32MutableSpan();
ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData);
MutableSpan<float> dest = new MutableSpan<float>(64);
MutableSpan<float> temp = new MutableSpan<float>(64);
ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp);
for (int i = 0; i < 64; i++)
{
float expected = intData[i];
float actual = dest[i];
Assert.Equal(expected, actual, new ApproximateFloatComparer(1f));
}
}
[Theory]
[InlineData(42, 0)]
[InlineData(1, 0)]
[InlineData(2, 0)]
public void IntegerDCT_ForwardThenInverse(int seed, int startAt)
{
MutableSpan<int> original = Create8x8RandomIntData(-200, 200, seed);
var block = original.AddScalarToAllValues(128);
ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block);
for (int i = 0; i < 64; i++)
{
block[i] /= 8;
}
ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block);
for (int i = startAt; i < 64; i++)
{
float expected = original[i];
float actual = (float)block[i];
Assert.Equal(expected, actual, new ApproximateFloatComparer(3f));
}
}
[Theory]
[InlineData(42, 0)]
[InlineData(1, 0)]
[InlineData(2, 0)]
public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt)
{
var data = Create8x8RandomIntData(-200, 200, seed);
MutableSpan<float> src = new MutableSpan<int>(data).ConvertToFloat32MutableSpan();
MutableSpan<float> dest = new MutableSpan<float>(64);
MutableSpan<float> temp = new MutableSpan<float>(64);
ReferenceImplementations.fDCT2D_llm(src, dest, temp, true);
ReferenceImplementations.iDCT2D_llm(dest, src, temp);
for (int i = startAt; i < 64; i++)
{
float expected = data[i];
float actual = (float)src[i];
Assert.Equal(expected, actual, new ApproximateFloatComparer(2f));
}
}
[Fact]
public void HowMuchIsTheFish()
{
Output.WriteLine(Vector<int>.Count.ToString());
}
[Theory]
[InlineData(42)]
[InlineData(1)]
[InlineData(2)]
public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed)
{
MutableSpan<int> intData = Create8x8RandomIntData(-200, 200, seed);
MutableSpan<float> floatSrc = intData.ConvertToFloat32MutableSpan();
ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData);
MutableSpan<float> dest = new MutableSpan<float>(64);
MutableSpan<float> temp = new MutableSpan<float>(64);
ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true);
for (int i = 0; i < 64; i++)
{
float expected = intData[i];
float actual = dest[i];
Assert.Equal(expected, actual, new ApproximateFloatComparer(1f));
}
}
}
}

107
tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs

@ -1,21 +1,53 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text;
using ImageSharp.Formats;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
namespace ImageSharp.Tests
{
public class UtilityTestClassBase
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using ImageSharp.Formats.Jpg;
/// <summary>
/// Utility class to measure the execution of an operation.
/// </summary>
public class MeasureFixture
{
public UtilityTestClassBase(ITestOutputHelper output)
protected bool EnablePrinting = true;
protected void Measure(int times, Action action, [CallerMemberName] string operationName = null)
{
Output = output;
if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} X {times} ...");
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < times; i++)
{
action();
}
sw.Stop();
if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms");
}
public MeasureFixture(ITestOutputHelper output)
{
this.Output = output;
}
protected ITestOutputHelper Output { get; }
}
public class UtilityTestClassBase : MeasureFixture
{
public UtilityTestClassBase(ITestOutputHelper output) : base(output)
{
}
// ReSharper disable once InconsistentNaming
public static float[] Create8x8FloatData()
{
@ -29,10 +61,7 @@ namespace ImageSharp.Tests.Formats.Jpg
}
return result;
}
// ReSharper disable once InconsistentNaming
public static int[] Create8x8IntData()
{
@ -47,7 +76,25 @@ namespace ImageSharp.Tests.Formats.Jpg
return result;
}
internal void Print8x8Data<T>(MutableSpan<T> data) => Print8x8Data(data.Data);
// ReSharper disable once InconsistentNaming
public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42)
{
Random rnd = new Random(seed);
int[] result = new int[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
result[i * 8 + j] = rnd.Next(minValue, maxValue);
}
}
return result;
}
internal static MutableSpan<float> Create8x8RandomFloatData(int minValue, int maxValue, int seed = 42)
=> new MutableSpan<int>(Create8x8RandomIntData(minValue, maxValue, seed)).ConvertToFloat32MutableSpan();
internal void Print8x8Data<T>(MutableSpan<T> data) => this.Print8x8Data(data.Data);
internal void Print8x8Data<T>(T[] data)
{
@ -61,10 +108,10 @@ namespace ImageSharp.Tests.Formats.Jpg
bld.AppendLine();
}
Output.WriteLine(bld.ToString());
this.Output.WriteLine(bld.ToString());
}
internal void PrintLinearData<T>(T[] data) => PrintLinearData(new MutableSpan<T>(data), data.Length);
internal void PrintLinearData<T>(T[] data) => this.PrintLinearData(new MutableSpan<T>(data), data.Length);
internal void PrintLinearData<T>(MutableSpan<T> data, int count = -1)
{
@ -75,21 +122,35 @@ namespace ImageSharp.Tests.Formats.Jpg
{
bld.Append($"{data[i],3} ");
}
Output.WriteLine(bld.ToString());
this.Output.WriteLine(bld.ToString());
}
protected void Measure(int times, Action action, [CallerMemberName] string operationName = null)
internal struct ApproximateFloatComparer : IEqualityComparer<float>
{
Output.WriteLine($"{operationName} X {times} ...");
Stopwatch sw = Stopwatch.StartNew();
private readonly float Eps;
for (int i = 0; i < times; i++)
public ApproximateFloatComparer(float eps = 1f)
{
action();
this.Eps = eps;
}
sw.Stop();
Output.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms");
public bool Equals(float x, float y)
{
float d = x - y;
return d > -this.Eps && d < this.Eps;
}
public int GetHashCode(float obj)
{
throw new InvalidOperationException();
}
}
protected void Print(string msg)
{
Debug.WriteLine(msg);
this.Output.WriteLine(msg);
}
}
}

7
tests/ImageSharp.Tests/TestFile.cs

@ -34,12 +34,17 @@ namespace ImageSharp.Tests
this.Bytes = File.ReadAllBytes(file);
this.image = new Image(this.Bytes);
}
public static string GetPath(string file)
{
return Path.Combine(FormatsDirectory, file);
}
public static TestFile Create(string file)
{
return cache.GetOrAdd(file, (string fileName) =>
{
return new TestFile(FormatsDirectory + fileName);
return new TestFile(GetPath(file));
});
}

2
tests/ImageSharp.Tests/TestImages.cs

@ -56,7 +56,7 @@ namespace ImageSharp.Tests
public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg";
public static readonly string[] All = {
Cmyk, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray,
Cmyk, Ycck, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray,
Festzug, Hiyamugi,
Jpeg400, Jpeg420, Jpeg444,
};

5
tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs

@ -23,5 +23,10 @@ namespace ImageSharp.Tests
{
return new Image<TColor>(bytes);
}
public virtual Image<TColor> CreateImage(Image<TColor> other)
{
return new Image<TColor>(other);
}
}
}

6
tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs

@ -10,5 +10,11 @@ namespace ImageSharp.Tests
public override Image<Color> CreateImage(byte[] bytes) => new Image(bytes);
public override Image<Color> CreateImage(int width, int height) => new Image(width, height);
public override Image<Color> CreateImage(Image<Color> other)
{
Image img = (Image)other;
return new Image(img);
}
}
}

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

@ -13,8 +13,18 @@ namespace ImageSharp.Tests
{
private class FileProvider : TestImageProvider<TColor>
{
private static ConcurrentDictionary<string, Image<TColor>> cache =
new ConcurrentDictionary<string, Image<TColor>>();
// Need PixelTypes in the dictionary key, because result images of TestImageProvider<TColor>.FileProvider
// are shared between PixelTypes.Color & PixelTypes.StandardImageClass
private class Key : Tuple<PixelTypes, string>
{
public Key(PixelTypes item1, string item2)
: base(item1, item2)
{
}
}
private static ConcurrentDictionary<Key, Image<TColor>> cache =
new ConcurrentDictionary<Key, Image<TColor>>();
private string filePath;
@ -27,15 +37,17 @@ namespace ImageSharp.Tests
public override Image<TColor> GetImage()
{
var key = new Key(this.PixelType, this.filePath);
var cachedImage = cache.GetOrAdd(
this.filePath,
key,
fn =>
{
var testFile = TestFile.Create(this.filePath);
return this.Factory.CreateImage(testFile.Bytes);
});
return new Image<TColor>(cachedImage);
return this.Factory.CreateImage(cachedImage);
}
}
}

8
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs

@ -80,7 +80,7 @@ namespace ImageSharp.Tests
this.Utility = new ImagingTestCaseUtility()
{
SourceFileOrDescription = this.SourceFileOrDescription,
PixelTypeName = typeof(TColor).Name
PixelTypeName = this.PixelType.ToString()
};
if (testMethod != null)
@ -90,5 +90,11 @@ namespace ImageSharp.Tests
return this;
}
public override string ToString()
{
string provName = this.GetType().Name.Replace("Provider", "");
return $"{this.SourceFileOrDescription}[{this.PixelType}]";
}
}
}

1
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -57,6 +57,7 @@ namespace ImageSharp.Tests
[Theory]
[WithBlankImages(1, 1, PixelTypes.StandardImageClass)]
[WithFile(TestImages.Bmp.F, PixelTypes.StandardImageClass)]
public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass<TColor>(
TestImageProvider<TColor> provider)
where TColor : struct, IPackedPixel, IEquatable<TColor>

Loading…
Cancel
Save