Browse Source

Merge branch 'refs/heads/pr/25'

# Conflicts:
#	src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
af/merge-core
James Jackson-South 10 years ago
parent
commit
23f96d1a1a
  1. 73
      src/ImageSharp/Formats/Jpg/Components/Bits.cs
  2. 188
      src/ImageSharp/Formats/Jpg/Components/Block.cs
  3. 52
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs
  4. 80
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt
  5. 405
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
  6. 141
      src/ImageSharp/Formats/Jpg/Components/Bytes.cs
  7. 10
      src/ImageSharp/Formats/Jpg/Components/Component.cs
  8. 2
      src/ImageSharp/Formats/Jpg/Components/FDCT.cs
  9. 53
      src/ImageSharp/Formats/Jpg/Components/Huffman.cs
  10. 3
      src/ImageSharp/Formats/Jpg/Components/IDCT.cs
  11. 108
      src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs
  12. 6
      src/ImageSharp/Formats/Jpg/JpegDecoder.cs
  13. 1143
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  14. 63
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  15. 2
      tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
  16. 367
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  17. 85
      tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
  18. 335
      tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs
  19. 95
      tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs
  20. 2
      tests/ImageSharp.Tests/Formats/Png/PngTests.cs
  21. 11
      tests/ImageSharp.Tests/TestImages.cs

73
src/ImageSharp/Formats/Jpg/Components/Bits.cs

@ -5,27 +5,92 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Holds the unprocessed bits that have been taken from the byte-stream. /// Holds the unprocessed bits that have been taken from the byte-stream.
/// The n least significant bits of a form the unread bits, to be read in MSB to /// The n least significant bits of a form the unread bits, to be read in MSB to
/// LSB order. /// LSB order.
/// </summary> /// </summary>
internal class Bits internal struct Bits
{ {
/// <summary> /// <summary>
/// Gets or sets the accumulator. /// Gets or sets the accumulator.
/// </summary> /// </summary>
public uint Accumulator { get; set; } public uint Accumulator;
/// <summary> /// <summary>
/// Gets or sets the mask. /// Gets or sets the mask.
/// <![CDATA[mask==1<<(unreadbits-1) when unreadbits>0, with mask==0 when unreadbits==0.]]> /// <![CDATA[mask==1<<(unreadbits-1) when unreadbits>0, with mask==0 when unreadbits==0.]]>
/// </summary> /// </summary>
public uint Mask { get; set; } public uint Mask;
/// <summary> /// <summary>
/// Gets or sets the number of unread bits in the accumulator. /// Gets or sets the number of unread bits in the accumulator.
/// </summary> /// </summary>
public int UnreadBits { get; set; } public int UnreadBits;
/// <summary>
/// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
/// least n. For best performance (avoiding function calls inside hot loops),
/// the caller is the one responsible for first checking that bits.UnreadBits &lt; n.
/// </summary>
/// <param name="n">The number of bits to ensure.</param>
/// <param name="decoder"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal JpegDecoderCore.ErrorCodes EnsureNBits(int n, JpegDecoderCore decoder)
{
while (true)
{
JpegDecoderCore.ErrorCodes errorCode;
byte c = decoder.bytes.ReadByteStuffedByte(decoder.inputStream, out errorCode);
if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
{
return errorCode;
}
this.Accumulator = (this.Accumulator << 8) | c;
this.UnreadBits += 8;
if (this.Mask == 0)
{
this.Mask = 1 << 7;
}
else
{
this.Mask <<= 8;
}
if (this.UnreadBits >= n)
{
return JpegDecoderCore.ErrorCodes.NoError;
}
}
}
internal int ReceiveExtend(byte t, JpegDecoderCore decoder)
{
if (this.UnreadBits < t)
{
var errorCode = this.EnsureNBits(t, decoder);
if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
{
throw new JpegDecoderCore.MissingFF00Exception();
}
}
this.UnreadBits -= t;
this.Mask >>= t;
int s = 1 << t;
int x = (int)((this.Accumulator >> this.UnreadBits) & (s - 1));
if (x < (s >> 1))
{
x += ((-1) << t) + 1;
}
return x;
}
} }
} }

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

@ -2,14 +2,19 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Represents an 8x8 block of coefficients to transform and encode. /// Represents an 8x8 block of coefficients to transform and encode.
/// </summary> /// </summary>
internal class Block internal struct Block : IDisposable
{ {
private static readonly ArrayPool<int> ArrayPool = ArrayPool<int>.Create(BlockSize, 50);
/// <summary> /// <summary>
/// Gets the size of the block. /// Gets the size of the block.
/// </summary> /// </summary>
@ -18,16 +23,137 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The array of block data. /// The array of block data.
/// </summary> /// </summary>
private readonly int[] data; public int[] Data;
public void Init()
{
// this.Data = new int[BlockSize];
this.Data = ArrayPool.Rent(BlockSize);
}
public static Block Create()
{
var block = new Block();
block.Init();
return block;
}
public static Block[] CreateArray(int size)
{
Block[] result = new Block[size];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
public bool IsInitialized => this.Data != null;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Block"/> class. /// Gets the pixel data at the given block index.
/// </summary> /// </summary>
public Block() /// <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;
}
}
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
public void Dispose()
{
if (this.Data != null)
{
ArrayPool.Return(this.Data, true);
this.Data = null;
}
}
public static void DisposeAll(Block[] blocks)
{ {
this.data = new int[BlockSize]; for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
} }
public void Clear()
{
for (int i = 0; i < this.Data.Length; i++)
{
this.Data[i] = 0;
}
}
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
{
private static readonly ArrayPool<float> ArrayPool = ArrayPool<float>.Create(BlockSize, 50);
/// <summary>
/// Size of the block.
/// </summary>
public const int BlockSize = 64;
/// <summary>
/// The array of block data.
/// </summary>
public float[] Data;
public void Init()
{
// this.Data = new int[BlockSize];
this.Data = ArrayPool.Rent(BlockSize);
}
public static BlockF Create()
{
var block = new BlockF();
block.Init();
return block;
}
public static BlockF[] CreateArray(int size)
{
BlockF[] result = new BlockF[size];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
public bool IsInitialized => this.Data != null;
/// <summary> /// <summary>
/// Gets the pixel data at the given block index. /// Gets the pixel data at the given block index.
/// </summary> /// </summary>
@ -35,10 +161,52 @@ namespace ImageSharp.Formats
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/>.
/// </returns> /// </returns>
public int this[int index] public float this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Data[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.Data[index] = value;
}
}
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
public void Dispose()
{
if (this.Data != null)
{
ArrayPool.Return(this.Data, true);
this.Data = null;
}
}
public static void DisposeAll(BlockF[] blocks)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
}
public void Clear()
{
for (int i = 0; i < this.Data.Length; i++)
{
this.Data[i] = 0;
}
}
public BlockF Clone()
{ {
get { return this.data[index]; } BlockF clone = Create();
set { this.data[index] = value; } Array.Copy(this.Data, clone.Data, BlockSize);
return clone;
} }
} }
} }

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

@ -0,0 +1,52 @@
// <auto-generated />
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace ImageSharp.Formats
{
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(-128f);
private static readonly Vector4 CMax4 = new Vector4(127f);
private static readonly Vector4 COff4 = new Vector4(128f);
/// <summary>
/// Transpose the block into d
/// </summary>
/// <param name="d">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void TransposeInto(ref Block8x8F d)
{
d.V0L.X = V0L.X; d.V1L.X = V0L.Y; d.V2L.X = V0L.Z; d.V3L.X = V0L.W; d.V4L.X = V0R.X; d.V5L.X = V0R.Y; d.V6L.X = V0R.Z; d.V7L.X = V0R.W;
d.V0L.Y = V1L.X; d.V1L.Y = V1L.Y; d.V2L.Y = V1L.Z; d.V3L.Y = V1L.W; d.V4L.Y = V1R.X; d.V5L.Y = V1R.Y; d.V6L.Y = V1R.Z; d.V7L.Y = V1R.W;
d.V0L.Z = V2L.X; d.V1L.Z = V2L.Y; d.V2L.Z = V2L.Z; d.V3L.Z = V2L.W; d.V4L.Z = V2R.X; d.V5L.Z = V2R.Y; d.V6L.Z = V2R.Z; d.V7L.Z = V2R.W;
d.V0L.W = V3L.X; d.V1L.W = V3L.Y; d.V2L.W = V3L.Z; d.V3L.W = V3L.W; d.V4L.W = V3R.X; d.V5L.W = V3R.Y; d.V6L.W = V3R.Z; d.V7L.W = V3R.W;
d.V0R.X = V4L.X; d.V1R.X = V4L.Y; d.V2R.X = V4L.Z; d.V3R.X = V4L.W; d.V4R.X = V4R.X; d.V5R.X = V4R.Y; d.V6R.X = V4R.Z; d.V7R.X = V4R.W;
d.V0R.Y = V5L.X; d.V1R.Y = V5L.Y; d.V2R.Y = V5L.Z; d.V3R.Y = V5L.W; d.V4R.Y = V5R.X; d.V5R.Y = V5R.Y; d.V6R.Y = V5R.Z; d.V7R.Y = V5R.W;
d.V0R.Z = V6L.X; d.V1R.Z = V6L.Y; d.V2R.Z = V6L.Z; d.V3R.Z = V6L.W; d.V4R.Z = V6R.X; d.V5R.Z = V6R.Y; d.V6R.Z = V6R.Z; d.V7R.Z = V6R.W;
d.V0R.W = V7L.X; d.V1R.W = V7L.Y; d.V2R.W = V7L.Z; d.V3R.W = V7L.W; d.V4R.W = V7R.X; d.V5R.W = V7R.Y; d.V6R.W = V7R.Z; d.V7R.W = V7R.W;
}
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// </summary>
/// <param name="d">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
{
d.V0L = Vector4.Max(Vector4.Min(V0L, CMax4), CMin4) + COff4;d.V0R = Vector4.Max(Vector4.Min(V0R, CMax4), CMin4) + COff4;
d.V1L = Vector4.Max(Vector4.Min(V1L, CMax4), CMin4) + COff4;d.V1R = Vector4.Max(Vector4.Min(V1R, CMax4), CMin4) + COff4;
d.V2L = Vector4.Max(Vector4.Min(V2L, CMax4), CMin4) + COff4;d.V2R = Vector4.Max(Vector4.Min(V2R, CMax4), CMin4) + COff4;
d.V3L = Vector4.Max(Vector4.Min(V3L, CMax4), CMin4) + COff4;d.V3R = Vector4.Max(Vector4.Min(V3R, CMax4), CMin4) + COff4;
d.V4L = Vector4.Max(Vector4.Min(V4L, CMax4), CMin4) + COff4;d.V4R = Vector4.Max(Vector4.Min(V4R, CMax4), CMin4) + COff4;
d.V5L = Vector4.Max(Vector4.Min(V5L, CMax4), CMin4) + COff4;d.V5R = Vector4.Max(Vector4.Min(V5R, CMax4), CMin4) + COff4;
d.V6L = Vector4.Max(Vector4.Min(V6L, CMax4), CMin4) + COff4;d.V6R = Vector4.Max(Vector4.Min(V6R, CMax4), CMin4) + COff4;
d.V7L = Vector4.Max(Vector4.Min(V7L, CMax4), CMin4) + COff4;d.V7R = Vector4.Max(Vector4.Min(V7R, CMax4), CMin4) + COff4;
}
}
}

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

@ -0,0 +1,80 @@
<#@ 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 />
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
<#
char[] coordz = {'X', 'Y', 'Z', 'W'};
#>
namespace ImageSharp.Formats
{
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(-128f);
private static readonly Vector4 CMax4 = new Vector4(127f);
private static readonly Vector4 COff4 = new Vector4(128f);
/// <summary>
/// Transpose the block into d
/// </summary>
/// <param name="d">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void TransposeInto(ref Block8x8F d)
{
<#
PushIndent(" ");
for (int i = 0; i < 8; i++)
{
char destCoord = coordz[i % 4];
char destSide = (i / 4) % 2 == 0 ? 'L' : 'R';
for (int j = 0; j < 8; j++)
{
char srcCoord = coordz[j % 4];
char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R';
string expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord}; ";
Write(expression);
}
WriteLine("");
}
PopIndent();
#>
}
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// </summary>
/// <param name="d">Destination</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
{
<#
PushIndent(" ");
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 2; j++)
{
char side = j == 0 ? 'L' : 'R';
Write($"d.V{i}{side} = Vector4.Max(Vector4.Min(V{i}{side}, CMax4), CMin4) + COff4;");
}
WriteLine("");
}
PopIndent();
#>
}
}
}

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

@ -0,0 +1,405 @@
// <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
namespace ImageSharp.Formats
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <summary>
/// DCT code Ported from https://github.com/norishigefukushima/dct_simd
/// </summary>
internal partial struct Block8x8F
{
public Vector4 V0L;
public Vector4 V0R;
public Vector4 V1L;
public Vector4 V1R;
public Vector4 V2L;
public Vector4 V2R;
public Vector4 V3L;
public Vector4 V3R;
public Vector4 V4L;
public Vector4 V4R;
public Vector4 V5L;
public Vector4 V5R;
public Vector4 V6L;
public Vector4 V6R;
public Vector4 V7L;
public Vector4 V7R;
public const int VectorCount = 16;
public const int ScalarCount = VectorCount*4;
/// <summary>
/// Load raw 32bit floating point data from source
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void LoadFrom(MutableSpan<float> source)
{
fixed (void* ptr = &this.V0L)
{
Marshal.Copy(source.Data, source.Offset, (IntPtr)ptr, ScalarCount);
}
}
/// <summary>
/// Load raw 32bit floating point data from source
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void LoadFrom(Block8x8F* blockPtr, MutableSpan<float> source)
{
Marshal.Copy(source.Data, source.Offset, (IntPtr)blockPtr, ScalarCount);
}
/// <summary>
/// Load raw 32bit floating point data from source
/// </summary>
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>
/// Copy raw 32bit floating point data to dest
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyTo(MutableSpan<float> dest)
{
fixed (void* ptr = &this.V0L)
{
Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount);
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyTo(float[] dest)
{
fixed (void* ptr = &this.V0L)
{
Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount);
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan<float> dest)
{
Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount);
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// </summary>
internal unsafe void CopyTo(MutableSpan<int> dest)
{
fixed (Vector4* ptr = &this.V0L)
{
float* fp = (float*)ptr;
for (int i = 0; i < ScalarCount; i++)
{
dest[i] = (int)fp[i];
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyAllInplace(Vector4 s)
{
this.V0L *= s;
this.V0R *= s;
this.V1L *= s;
this.V1R *= s;
this.V2L *= s;
this.V2R *= s;
this.V3L *= s;
this.V3R *= s;
this.V4L *= s;
this.V4R *= s;
this.V5L *= s;
this.V5R *= s;
this.V6L *= s;
this.V6R *= s;
this.V7L *= s;
this.V7R *= s;
}
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
/// </summary>
/// <param name="dest">Destination</param>
/// <param name="temp">Temporary block provided by the caller</param>
public void TransformIDCTInto(ref Block8x8F dest, ref Block8x8F temp)
{
this.TransposeInto(ref temp);
temp.IDCT8x4_LeftPart(ref dest);
temp.IDCT8x4_RightPart(ref dest);
dest.TransposeInto(ref temp);
temp.IDCT8x4_LeftPart(ref dest);
temp.IDCT8x4_RightPart(ref dest);
dest.MultiplyAllInplace(c_0_125);
}
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);
/// <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
/// </summary>
/// <param name="d">Destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void IDCT8x4_LeftPart(ref Block8x8F d)
{
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;
}
/// <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
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void IDCT8x4_RightPart(ref Block8x8F d)
{
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;
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;
}
public unsafe float this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
fixed (Block8x8F* p = &this)
{
float* fp = (float*)p;
return fp[idx];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
fixed (Block8x8F* p = &this)
{
float* fp = (float*)p;
fp[idx] = value;
}
}
}
/// <summary>
/// Pointer-based "Indexer" (getter part)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
{
float* fp = (float*)blockPtr;
return fp[idx];
}
/// <summary>
/// Pointer-based "Indexer" (setter part)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
{
float* fp = (float*)blockPtr;
fp[idx] = value;
}
/// <summary>
/// Fill the block with defaults (zeroes)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Clear()
{
// The cheapest way to do this in C#:
this = new Block8x8F();
}
/// <summary>
/// TODO: Should be removed when BlockF goes away
/// </summary>
/// <param name="legacyBlock"></param>
internal void LoadFrom(ref BlockF legacyBlock)
{
this.LoadFrom(legacyBlock.Data);
}
/// <summary>
/// TODO: Should be removed when BlockF goes away
/// </summary>
/// <param name="legacyBlock"></param>
internal void CopyTo(ref BlockF legacyBlock)
{
this.CopyTo(legacyBlock.Data);
}
/// <summary>
/// Level shift by +128, clip to [0, 255], and write to buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void CopyColorsTo(MutableSpan<byte> buffer, int stride, Block8x8F* temp)
{
this.TransformByteConvetibleColorValuesInto(ref *temp);
float* src = (float*)temp;
for (int i = 0; i < 8; i++)
{
buffer[0] = (byte)src[0];
buffer[1] = (byte)src[1];
buffer[2] = (byte)src[2];
buffer[3] = (byte)src[3];
buffer[4] = (byte)src[4];
buffer[5] = (byte)src[5];
buffer[6] = (byte)src[6];
buffer[7] = (byte)src[7];
buffer.AddOffset(stride);
src += 8;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void UnZig(Block8x8F* block, Block8x8F* qt, int* unzigPtr)
{
float* b = (float*)block;
float* qtp = (float*)qt;
for (int zig = 0; zig < BlockF.BlockSize; zig++)
{
float* unzigPos = b + unzigPtr[zig];
float val = *unzigPos;
val *= qtp[zig];
*unzigPos = val;
}
}
}
}

141
src/ImageSharp/Formats/Jpg/Components/Bytes.cs

@ -2,25 +2,28 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Bytes is a byte buffer, similar to a stream, except that it /// Bytes is a byte buffer, similar to a stream, except that it
/// has to be able to unread more than 1 byte, due to byte stuffing. /// has to be able to unread more than 1 byte, due to byte stuffing.
/// Byte stuffing is specified in section F.1.2.3. /// Byte stuffing is specified in section F.1.2.3.
/// </summary> /// </summary>
internal class Bytes internal struct Bytes : IDisposable
{ {
private static readonly ArrayPool<byte> ArrayPool = ArrayPool<byte>.Create(4096, 50);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Bytes"/> class. /// Creates a new instance of the <see cref="Bytes"/>, and initializes it's buffer.
/// </summary> /// </summary>
public Bytes() public static Bytes Create()
{ {
this.Buffer = new byte[4096]; return new Bytes { Buffer = ArrayPool.Rent(4096) };
this.I = 0;
this.J = 0;
this.UnreadableBytes = 0;
} }
/// <summary> /// <summary>
@ -28,16 +31,128 @@ namespace ImageSharp.Formats
/// buffer[i:j] are the buffered bytes read from the underlying /// buffer[i:j] are the buffered bytes read from the underlying
/// stream that haven't yet been passed further on. /// stream that haven't yet been passed further on.
/// </summary> /// </summary>
public byte[] Buffer { get; set; } public byte[] Buffer;
public int I { get; set; } public int I;
public int J { get; set; } public int J;
/// <summary> /// <summary>
/// Gets or sets the unreadable bytes. The number of bytes to back up i after /// Gets or sets the unreadable bytes. The number of bytes to back up i after
/// overshooting. It can be 0, 1 or 2. /// overshooting. It can be 0, 1 or 2.
/// </summary> /// </summary>
public int UnreadableBytes { get; set; } public int UnreadableBytes;
public void Dispose()
{
if (this.Buffer != null) ArrayPool.Return(this.Buffer);
this.Buffer = null;
}
/// <summary>
/// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
/// </summary>
/// <returns>The <see cref="byte"/></returns>
internal byte ReadByteStuffedByte(Stream inputStream, out JpegDecoderCore.ErrorCodes errorCode)
{
byte x;
errorCode = JpegDecoderCore.ErrorCodes.NoError;
// Take the fast path if bytes.buf contains at least two bytes.
if (this.I + 2 <= this.J)
{
x = this.Buffer[this.I];
this.I++;
this.UnreadableBytes = 1;
if (x != JpegConstants.Markers.XFF)
{
return x;
}
if (this.Buffer[this.I] != 0x00)
{
errorCode = JpegDecoderCore.ErrorCodes.MissingFF00;
return 0;
// throw new MissingFF00Exception();
}
this.I++;
this.UnreadableBytes = 2;
return JpegConstants.Markers.XFF;
}
this.UnreadableBytes = 0;
x = this.ReadByte(inputStream);
this.UnreadableBytes = 1;
if (x != JpegConstants.Markers.XFF)
{
return x;
}
x = this.ReadByte(inputStream);
this.UnreadableBytes = 2;
if (x != 0x00)
{
errorCode = JpegDecoderCore.ErrorCodes.MissingFF00;
return 0;
// throw new MissingFF00Exception();
}
return JpegConstants.Markers.XFF;
}
/// <summary>
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
/// </summary>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal byte ReadByte(Stream inputStream)
{
while (this.I == this.J)
{
this.Fill(inputStream);
}
byte x = this.Buffer[this.I];
this.I++;
this.UnreadableBytes = 0;
return x;
}
/// <summary>
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Fill(Stream inputStream)
{
if (this.I != this.J)
{
throw new ImageFormatException("Fill called when unread bytes exist.");
}
// Move the last 2 bytes to the start of the buffer, in case we need
// to call UnreadByteStuffedByte.
if (this.J > 2)
{
this.Buffer[0] = this.Buffer[this.J - 2];
this.Buffer[1] = this.Buffer[this.J - 1];
this.I = 2;
this.J = 2;
}
// Fill in the rest of the buffer.
int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J);
if (n == 0)
{
throw new JpegDecoderCore.EOFException();
}
this.J += n;
}
} }
} }

10
src/ImageSharp/Formats/Jpg/Components/Component.cs

@ -8,26 +8,26 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Represents a single color component /// Represents a single color component
/// </summary> /// </summary>
internal class Component internal struct Component
{ {
/// <summary> /// <summary>
/// Gets or sets the horizontal sampling factor. /// Gets or sets the horizontal sampling factor.
/// </summary> /// </summary>
public int HorizontalFactor { get; set; } public int HorizontalFactor;
/// <summary> /// <summary>
/// Gets or sets the vertical sampling factor. /// Gets or sets the vertical sampling factor.
/// </summary> /// </summary>
public int VerticalFactor { get; set; } public int VerticalFactor;
/// <summary> /// <summary>
/// Gets or sets the identifier /// Gets or sets the identifier
/// </summary> /// </summary>
public byte Identifier { get; set; } public byte Identifier;
/// <summary> /// <summary>
/// Gets or sets the quantization table destination selector. /// Gets or sets the quantization table destination selector.
/// </summary> /// </summary>
public byte Selector { get; set; } public byte Selector;
} }
} }

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

@ -45,7 +45,7 @@ namespace ImageSharp.Formats
/// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift.
/// </summary> /// </summary>
/// <param name="block">The block of coefficients.</param> /// <param name="block">The block of coefficients.</param>
public static void Transform(Block block) public static void Transform(ref Block block)
{ {
// Pass 1: process rows. // Pass 1: process rows.
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)

53
src/ImageSharp/Formats/Jpg/Components/Huffman.cs

@ -2,34 +2,36 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Buffers;
/// <summary> /// <summary>
/// Represents a Huffman tree /// Represents a Huffman tree
/// </summary> /// </summary>
internal class Huffman internal struct Huffman : IDisposable
{ {
/// <summary> private static ArrayPool<ushort> UshortBuffer = ArrayPool<ushort>.Create(1 << JpegDecoderCore.LutSize, 50);
/// Initializes a new instance of the <see cref="Huffman"/> class.
/// </summary> private static ArrayPool<byte> ByteBuffer = ArrayPool<byte>.Create(JpegDecoderCore.MaxNCodes, 50);
/// <param name="lutSize">The log-2 size of the Huffman decoder's look-up table.</param>
/// <param name="maxNCodes">The maximum (inclusive) number of codes in a Huffman tree.</param> private static readonly ArrayPool<int> IntBuffer = ArrayPool<int>.Create(JpegDecoderCore.MaxCodeLength, 50);
/// <param name="maxCodeLength">The maximum (inclusive) number of bits in a Huffman code.</param>
public Huffman(int lutSize, int maxNCodes, int maxCodeLength) public void Init(int lutSize, int maxNCodes, int maxCodeLength)
{ {
this.Lut = new ushort[1 << lutSize]; this.Lut = UshortBuffer.Rent(1 << lutSize);
this.Values = new byte[maxNCodes]; this.Values = ByteBuffer.Rent(maxNCodes);
this.MinCodes = new int[maxCodeLength]; this.MinCodes = IntBuffer.Rent(maxCodeLength);
this.MaxCodes = new int[maxCodeLength]; this.MaxCodes = IntBuffer.Rent(maxCodeLength);
this.Indices = new int[maxCodeLength]; this.Indices = IntBuffer.Rent(maxCodeLength);
this.Length = 0;
} }
/// <summary> /// <summary>
/// Gets or sets the number of codes in the tree. /// Gets or sets the number of codes in the tree.
/// </summary> /// </summary>
public int Length { get; set; } public int Length;
/// <summary> /// <summary>
/// Gets the look-up table for the next LutSize bits in the bit-stream. /// Gets the look-up table for the next LutSize bits in the bit-stream.
@ -37,28 +39,37 @@ namespace ImageSharp.Formats
/// are 1 plus the code length, or 0 if the value is too large to fit in /// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits. /// lutSize bits.
/// </summary> /// </summary>
public ushort[] Lut { get; } public ushort[] Lut;
/// <summary> /// <summary>
/// Gets the the decoded values, sorted by their encoding. /// Gets the the decoded values, sorted by their encoding.
/// </summary> /// </summary>
public byte[] Values { get; } public byte[] Values;
/// <summary> /// <summary>
/// Gets the array of minimum codes. /// Gets the array of minimum codes.
/// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
/// </summary> /// </summary>
public int[] MinCodes { get; } public int[] MinCodes;
/// <summary> /// <summary>
/// Gets the array of maximum codes. /// Gets the array of maximum codes.
/// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
/// </summary> /// </summary>
public int[] MaxCodes { get; } public int[] MaxCodes;
/// <summary> /// <summary>
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
/// </summary> /// </summary>
public int[] Indices { get; } public int[] Indices;
public void Dispose()
{
UshortBuffer.Return(this.Lut, true);
ByteBuffer.Return(this.Values, true);
IntBuffer.Return(this.MinCodes, true);
IntBuffer.Return(this.MaxCodes, true);
IntBuffer.Return(this.Indices, true);
}
} }
} }

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

@ -39,7 +39,7 @@ namespace ImageSharp.Formats
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
/// </summary> /// </summary>
/// <param name="src">The source block of coefficients</param> /// <param name="src">The source block of coefficients</param>
public static void Transform(Block src) public static void Transform(ref Block src)
{ {
// Horizontal 1-D IDCT. // Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)
@ -165,5 +165,6 @@ namespace ImageSharp.Formats
src[56 + x] = (y7 - y1) >> 14; src[56 + x] = (y7 - y1) >> 14;
} }
} }
} }
} }

108
src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs

@ -0,0 +1,108 @@
// <copyright file="MutableSpan.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.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Like corefxlab Span, but with an AddOffset() method for efficiency.
/// TODO: When Span will be official, consider replacing this class!
/// </summary>
/// <see>
/// <cref>https://github.com/dotnet/corefxlab/blob/master/src/System.Slices/System/Span.cs</cref>
/// </see>
/// <typeparam name="T"></typeparam>
internal struct MutableSpan<T>
{
public T[] Data;
public int Offset;
public int TotalCount => this.Data.Length - this.Offset;
public MutableSpan(int size, int offset = 0)
{
this.Data = new T[size];
this.Offset = offset;
}
public MutableSpan(T[] data, int offset = 0)
{
this.Data = data;
this.Offset = offset;
}
public T this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Data[idx + this.Offset];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.Data[idx + this.Offset] = value;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MutableSpan<T> Slice(int offset)
{
return new MutableSpan<T>(this.Data, this.Offset + offset);
}
public static implicit operator MutableSpan<T>(T[] data) => new MutableSpan<T>(data, 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddOffset(int offset)
{
this.Offset += offset;
}
}
internal static class MutableSpanExtensions
{
public static MutableSpan<T> Slice<T>(this T[] array, int offset) => new MutableSpan<T>(array, offset);
[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];
}
[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];
}
[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;
}
[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;
}
}
}

6
src/ImageSharp/Formats/Jpg/JpegDecoder.cs

@ -83,8 +83,10 @@ namespace ImageSharp.Formats
Guard.NotNull(image, "image"); Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream"); Guard.NotNull(stream, "stream");
JpegDecoderCore decoder = new JpegDecoderCore(); using (JpegDecoderCore decoder = new JpegDecoderCore())
decoder.Decode(image, stream, false); {
decoder.Decode(image, stream, false);
}
} }
/// <summary> /// <summary>

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

File diff suppressed because it is too large

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

@ -246,6 +246,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The AC luminance huffman table index /// The AC luminance huffman table index
/// </summary> /// </summary>
LuminanceAC = 1, LuminanceAC = 1,
// ReSharper restore UnusedMember.Local // ReSharper restore UnusedMember.Local
@ -487,9 +488,9 @@ namespace ImageSharp.Formats
/// <param name="index">The quantization table index.</param> /// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param> /// <param name="prevDC">The previous DC value.</param>
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
private int WriteBlock(Block block, QuantIndex index, int prevDC) private int WriteBlock(ref Block block, QuantIndex index, int prevDC)
{ {
FDCT.Transform(block); FDCT.Transform(ref block);
// Emit the DC delta. // Emit the DC delta.
int dc = Round(block[0], 8 * this.quant[(int)index][0]); int dc = Round(block[0], 8 * this.quant[(int)index][0]);
@ -540,7 +541,7 @@ namespace ImageSharp.Formats
/// <param name="cbBlock">The red chroma block.</param> /// <param name="cbBlock">The red chroma block.</param>
/// <param name="crBlock">The blue chroma block.</param> /// <param name="crBlock">The blue chroma block.</param>
// ReSharper disable StyleCop.SA1305 // ReSharper disable StyleCop.SA1305
private void ToYCbCr<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) private void ToYCbCr<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int x, int y, ref Block yBlock, ref Block cbBlock, ref Block crBlock)
// ReSharper restore StyleCop.SA1305 // ReSharper restore StyleCop.SA1305
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
@ -577,7 +578,7 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="destination">The destination block array</param> /// <param name="destination">The destination block array</param>
/// <param name="source">The source block array.</param> /// <param name="source">The source block array.</param>
private void Scale16X16To8X8(Block destination, Block[] source) private void Scale16X16To8X8(ref Block destination, Block[] source)
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@ -847,10 +848,10 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
Block b = new Block(); Block b = Block.Create();
Block cb = new Block(); Block cb = Block.Create();
Block cr = new Block(); Block cr = Block.Create();
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@ -858,12 +859,16 @@ namespace ImageSharp.Formats
{ {
for (int x = 0; x < pixels.Width; x += 8) for (int x = 0; x < pixels.Width; x += 8)
{ {
this.ToYCbCr(pixels, x, y, b, cb, cr); this.ToYCbCr(pixels, x, y, ref b, ref cb, ref cr);
prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb); prevDCCb = this.WriteBlock(ref cb, QuantIndex.Chrominance, prevDCCb);
prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr); prevDCCr = this.WriteBlock(ref cr, QuantIndex.Chrominance, prevDCCr);
} }
} }
b.Dispose();
cb.Dispose();
cr.Dispose();
} }
/// <summary> /// <summary>
@ -877,23 +882,13 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
Block b = new Block(); Block b = Block.Create();
Block[] cb = new Block[4]; Block[] cb = Block.CreateArray(4);
Block[] cr = new Block[4]; Block[] cr = Block.CreateArray(4);
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
for (int i = 0; i < 4; i++)
{
cb[i] = new Block();
}
for (int i = 0; i < 4; i++)
{
cr[i] = new Block();
}
for (int y = 0; y < pixels.Height; y += 16) for (int y = 0; y < pixels.Height; y += 16)
{ {
for (int x = 0; x < pixels.Width; x += 16) for (int x = 0; x < pixels.Width; x += 16)
@ -903,16 +898,20 @@ namespace ImageSharp.Formats
int xOff = (i & 1) * 8; int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4; int yOff = (i & 2) * 4;
this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]);
prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
} }
this.Scale16X16To8X8(b, cb); this.Scale16X16To8X8(ref b, cb);
prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); prevDCCb = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCb);
this.Scale16X16To8X8(b, cr); this.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); prevDCCr = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCr);
} }
} }
b.Dispose();
Block.DisposeAll(cb);
Block.DisposeAll(cr);
} }
/// <summary> /// <summary>

2
tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using ImageSharp.Formats;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using System.IO; using System.IO;

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

@ -0,0 +1,367 @@
// 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
{
public class Block8x8FTests : UtilityTestClassBase
{
#if BENCHMARKING
public const int Times = 1000000;
#else
public const int Times = 1;
#endif
public Block8x8FTests(ITestOutputHelper output) : base(output)
{
}
[Fact]
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];
}
});
Assert.Equal(sum, 64f * 63f * 0.5f);
}
[Fact]
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);
}
[Fact]
public void Indexer_ReferenceBenchmarkWithArray()
{
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);
}
[Fact]
public void Load_Store_FloatArray()
{
float[] data = new float[Block8x8F.ScalarCount];
float[] mirror = new float[Block8x8F.ScalarCount];
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
data[i] = i;
}
Measure(Times, () =>
{
Block8x8F b = new Block8x8F();
b.LoadFrom(data);
b.CopyTo(mirror);
});
Assert.Equal(data, mirror);
//PrintLinearData((MutableSpan<float>)mirror);
}
[Fact]
public unsafe void Load_Store_FloatArray_Ptr()
{
float[] data = new float[Block8x8F.ScalarCount];
float[] mirror = new float[Block8x8F.ScalarCount];
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
data[i] = i;
}
Measure(Times, () =>
{
Block8x8F b = new Block8x8F();
Block8x8F.LoadFrom(&b, data);
Block8x8F.CopyTo(&b, mirror);
});
Assert.Equal(data, mirror);
//PrintLinearData((MutableSpan<float>)mirror);
}
[Fact]
public void Load_Store_IntArray()
{
int[] data = new int[Block8x8F.ScalarCount];
int[] mirror = new int[Block8x8F.ScalarCount];
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
data[i] = i;
}
Measure(Times, () =>
{
Block8x8F v = new Block8x8F();
v.LoadFrom(data);
v.CopyTo(mirror);
});
Assert.Equal(data, mirror);
//PrintLinearData((MutableSpan<int>)mirror);
}
[Fact]
public void TransposeInto()
{
float[] expected = Create8x8FloatData();
ReferenceImplementations.Transpose8x8(expected);
Block8x8F source = new Block8x8F();
source.LoadFrom(Create8x8FloatData());
Block8x8F dest = new Block8x8F();
source.TransposeInto(ref dest);
float[] actual = new float[64];
dest.CopyTo(actual);
Assert.Equal(expected, actual);
}
private class BufferHolder
{
public Block8x8F Buffer;
}
[Fact]
public void TranposeInto_Benchmark()
{
BufferHolder source = new BufferHolder();
source.Buffer.LoadFrom(Create8x8FloatData());
BufferHolder dest = new BufferHolder();
Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ...");
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < Times; i++)
{
source.Buffer.TransposeInto(ref dest.Buffer);
}
sw.Stop();
Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
}
[Fact]
public void iDCT2D8x4_LeftPart()
{
float[] sourceArray = Create8x8FloatData();
float[] expectedDestArray = new float[64];
ReferenceImplementations.iDCT2D8x4_32f(sourceArray, expectedDestArray);
Block8x8F source = new Block8x8F();
source.LoadFrom(sourceArray);
Block8x8F dest = new Block8x8F();
source.IDCT8x4_LeftPart(ref dest);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
Print8x8Data(expectedDestArray);
Output.WriteLine("**************");
Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray, actualDestArray);
}
[Fact]
public void iDCT2D8x4_RightPart()
{
MutableSpan<float> sourceArray = Create8x8FloatData();
MutableSpan<float> expectedDestArray = new float[64];
ReferenceImplementations.iDCT2D8x4_32f(sourceArray.Slice(4), expectedDestArray.Slice(4));
Block8x8F source = new Block8x8F();
source.LoadFrom(sourceArray);
Block8x8F dest = new Block8x8F();
source.IDCT8x4_RightPart(ref dest);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
Print8x8Data(expectedDestArray);
Output.WriteLine("**************");
Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray.Data, actualDestArray);
}
private struct ApproximateFloatComparer : IEqualityComparer<float>
{
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();
float[] expectedDestArray = new float[64];
float[] tempArray = new float[64];
ReferenceImplementations.iDCT2D_llm(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);
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());
}
[Fact]
public unsafe void CopyColorsTo()
{
var data = Create8x8FloatData();
Block8x8F block = new Block8x8F();
block.LoadFrom(data);
block.MultiplyAllInplace(new Vector4(5, 5, 5, 5));
int stride = 256;
int height = 42;
int offset = height*10 + 20;
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: **********");
Assert.Equal(colorsExpected, colorsActual);
}
private static float[] Create8x8ColorCropTestData()
{
float[] result = new float[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
result[i * 8 + j] = -300 + i * 100 + j * 10;
}
}
return result;
}
[Fact]
public void TransformByteConvetibleColorValuesInto()
{
Block8x8F block = new Block8x8F();
var input = Create8x8ColorCropTestData();
block.LoadFrom(input);
Output.WriteLine("Input:");
PrintLinearData(input);
Block8x8F dest = new Block8x8F();
block.TransformByteConvetibleColorValuesInto(ref dest);
float[] array = new float[64];
dest.CopyTo(array);
Output.WriteLine("Result:");
PrintLinearData(array);
foreach (float val in array)
{
Assert.InRange(val, 0, 255);
}
}
}
}

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

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ImageSharp.Formats;
using Xunit;
using Xunit.Abstractions;
namespace ImageSharp.Tests.Formats.Jpg
{
public class JpegTests
{
public const string TestOutputDirectory = "TestOutput/Jpeg";
private ITestOutputHelper Output { get; }
public JpegTests(ITestOutputHelper output)
{
Output = output;
}
protected string CreateTestOutputFile(string fileName)
{
if (!Directory.Exists(TestOutputDirectory))
{
Directory.CreateDirectory(TestOutputDirectory);
}
//string id = Guid.NewGuid().ToString().Substring(0, 4);
string ext = Path.GetExtension(fileName);
fileName = Path.GetFileNameWithoutExtension(fileName);
return $"{TestOutputDirectory}/{fileName}{ext}";
}
protected Stream CreateOutputStream(string fileName)
{
fileName = CreateTestOutputFile(fileName);
Output?.WriteLine("Opened for write: "+fileName);
return File.OpenWrite(fileName);
}
public static IEnumerable<object[]> AllJpegFiles
=> TestImages.Jpeg.All.Select(fn => new object[] {fn});
[Theory]
[MemberData(nameof(AllJpegFiles))]
public void OpenJpeg_SaveBmp(string jpegPath)
{
string bmpFileName = Path.GetFileNameWithoutExtension(jpegPath) + ".bmp";
using (var inputStream = File.OpenRead(jpegPath))
{
var image = new Image(inputStream);
using (var outputStream = CreateOutputStream(bmpFileName))
{
image.Save(outputStream, new BmpFormat());
}
}
}
public static IEnumerable<object[]> AllBmpFiles
=> TestImages.Bmp.All.Select(fn => new object[] {fn});
[Theory]
[MemberData(nameof(AllBmpFiles))]
public void OpenBmp_SaveJpeg(string bmpPath)
{
string jpegPath = Path.GetFileNameWithoutExtension(bmpPath) + ".jpeg";
using (var inputStream = File.OpenRead(bmpPath))
{
var image = new Image(inputStream);
using (var outputStream = CreateOutputStream(jpegPath))
{
image.Save(outputStream, new JpegFormat());
}
}
}
}
}

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

@ -0,0 +1,335 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
{
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp.Formats;
/// <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
/// </summary>
internal static class ReferenceImplementations
{
/// <summary>
/// Transpose 8x8 block stored linearly in a span (inplace)
/// </summary>
/// <param name="data"></param>
internal static void Transpose8x8(MutableSpan<float> data)
{
for (int i = 1; i < 8; i++)
{
int i8 = i * 8;
for (int j = 0; j < i; j++)
{
float tmp = data[i8 + j];
data[i8 + j] = data[j * 8 + i];
data[j * 8 + i] = tmp;
}
}
}
/// <summary>
/// Transpose 8x8 block stored linearly in a span
/// </summary>
internal static void Transpose8x8(MutableSpan<float> src, MutableSpan<float> dest)
{
for (int i = 0; i < 8; i++)
{
int i8 = i * 8;
for (int j = 0; j < 8; j++)
{
dest[j * 8 + i] = src[i8 + j];
}
}
}
/// <summary>
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200
/// </summary>
/// <param name="y"></param>
/// <param name="x"></param>
private static void iDCT1Dllm_32f(MutableSpan<float> y, MutableSpan<float> x)
{
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;
z0 = y[1] + y[7];
z1 = y[3] + y[5];
z2 = y[3] + y[7];
z3 = y[1] + y[5];
z4 = (z0 + z1) * r3;
z0 = z0 * (-r3 + r7);
z1 = z1 * (-r3 - r1);
z2 = z2 * (-r3 - r5) + z4;
z3 = z3 * (-r3 + r5) + z4;
b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2;
b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3;
b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2;
b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3;
z4 = (y[2] + y[6]) * r6;
z0 = y[0] + y[4];
z1 = y[0] - y[4];
z2 = z4 - y[6] * (r2 + r6);
z3 = z4 + y[2] * (r2 - r6);
a0 = z0 + z3;
a3 = z0 - z3;
a1 = z1 + z2;
a2 = z1 - z2;
x[0] = a0 + b0;
x[7] = a0 - b0;
x[1] = a1 + b1;
x[6] = a1 - b1;
x[2] = a2 + b2;
x[5] = a2 - b2;
x[3] = a3 + b3;
x[4] = a3 - b3;
}
/// <summary>
/// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239
/// Applyies IDCT transformation on "s" copying transformed values to "d", using temporal block "temp"
/// </summary>
/// <param name="s"></param>
/// <param name="d"></param>
/// <param name="temp"></param>
internal static void iDCT2D_llm(MutableSpan<float> s, MutableSpan<float> d, MutableSpan<float> temp)
{
int j;
for (j = 0; j < 8; j++)
{
iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8));
}
Transpose8x8(temp, d);
for (j = 0; j < 8; j++)
{
iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8));
}
Transpose8x8(temp, d);
for (j = 0; j < 64; j++)
{
d[j] *= 0.125f;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 _mm_load_ps(MutableSpan<float> src, int offset)
{
src = src.Slice(offset);
return new Vector4(src[0], src[1], src[2], src[3]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void _mm_store_ps(MutableSpan<float> dest, int offset, Vector4 src)
{
dest = dest.Slice(offset);
dest[0] = src.X;
dest[1] = src.Y;
dest[2] = src.Z;
dest[3] = src.W;
}
private static readonly Vector4 _1_175876 = new Vector4(1.175876f);
private static readonly Vector4 _1_961571 = new Vector4(-1.961571f);
private static readonly Vector4 _0_390181 = new Vector4(-0.390181f);
private static readonly Vector4 _0_899976 = new Vector4(-0.899976f);
private static readonly Vector4 _2_562915 = new Vector4(-2.562915f);
private static readonly Vector4 _0_298631 = new Vector4(0.298631f);
private static readonly Vector4 _2_053120 = new Vector4(2.053120f);
private static readonly Vector4 _3_072711 = new Vector4(3.072711f);
private static readonly Vector4 _1_501321 = new Vector4(1.501321f);
private static readonly Vector4 _0_541196 = new Vector4(0.541196f);
private static readonly Vector4 _1_847759 = new Vector4(-1.847759f);
private static readonly Vector4 _0_765367 = new Vector4(0.765367f);
/// <summary>
/// Original:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// Does a part of the IDCT job on the given parts of the blocks
/// </summary>
/// <param name="y"></param>
/// <param name="x"></param>
internal static void iDCT2D8x4_32f(MutableSpan<float> y, MutableSpan<float> x)
{
/*
float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i;
for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); }
*/
/*
0: 1.414214
1: 1.387040
2: 1.306563
3:
4: 1.000000
5: 0.785695
6:
7: 0.275899
*/
Vector4 my1 = _mm_load_ps(y, 8);
Vector4 my7 = _mm_load_ps(y, 56);
Vector4 mz0 = my1 + my7;
Vector4 my3 = _mm_load_ps(y, 24);
Vector4 mz2 = my3 + my7;
Vector4 my5 = _mm_load_ps(y, 40);
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = ((mz0 + mz1) * _1_175876);
//z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5];
//z4 = (z0 + z1) * r[3];
mz2 = mz2 * _1_961571 + mz4;
mz3 = mz3 * _0_390181 + mz4;
mz0 = mz0 * _0_899976;
mz1 = mz1 * _2_562915;
/*
-0.899976
-2.562915
-1.961571
-0.390181
z0 = z0 * (-r[3] + r[7]);
z1 = z1 * (-r[3] - r[1]);
z2 = z2 * (-r[3] - r[5]) + z4;
z3 = z3 * (-r[3] + r[5]) + z4;*/
Vector4 mb3 = my7 * _0_298631 + mz0 + mz2;
Vector4 mb2 = my5 * _2_053120 + mz1 + mz3;
Vector4 mb1 = my3 * _3_072711 + mz1 + mz2;
Vector4 mb0 = my1 * _1_501321 + mz0 + mz3;
/*
0.298631
2.053120
3.072711
1.501321
b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2;
b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3;
b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2;
b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3;
*/
Vector4 my2 = _mm_load_ps(y, 16);
Vector4 my6 = _mm_load_ps(y, 48);
mz4 = (my2 + my6) * _0_541196;
Vector4 my0 = _mm_load_ps(y, 0);
Vector4 my4 = _mm_load_ps(y, 32);
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + my6 * _1_847759;
mz3 = mz4 + my2 * _0_765367;
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
/*
1.847759
0.765367
z4 = (y[2] + y[6]) * r[6];
z0 = y[0] + y[4]; z1 = y[0] - y[4];
z2 = z4 - y[6] * (r[2] + r[6]);
z3 = z4 + y[2] * (r[2] - r[6]);
a0 = z0 + z3; a3 = z0 - z3;
a1 = z1 + z2; a2 = z1 - z2;
*/
_mm_store_ps(x, 0, my0 + mb0);
_mm_store_ps(x, 56, my0 - mb0);
_mm_store_ps(x, 8, my1 + mb1);
_mm_store_ps(x, 48, my1 - mb1);
_mm_store_ps(x, 16, my2 + mb2);
_mm_store_ps(x, 40, my2 - mb2);
_mm_store_ps(x, 24, my3 + mb3);
_mm_store_ps(x, 32, my3 - mb3);
/*
x[0] = a0 + b0; x[7] = a0 - b0;
x[1] = a1 + b1; x[6] = a1 - b1;
x[2] = a2 + b2; x[5] = a2 - b2;
x[3] = a3 + b3; x[4] = a3 - b3;
for(i = 0;i < 8;i++){ x[i] *= 0.353554f; }
*/
}
/// <summary>
/// Copies color values from block to the destination image buffer.
/// </summary>
/// <param name="block"></param>
/// <param name="buffer"></param>
/// <param name="stride"></param>
internal static unsafe void CopyColorsTo(ref Block8x8F block, MutableSpan<byte> buffer, int stride)
{
fixed (Block8x8F* p = &block)
{
float* b = (float*)p;
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
int yStride = y * stride;
for (int x = 0; x < 8; x++)
{
float c = b[y8 + x];
if (c < -128)
{
c = 0;
}
else if (c > 127)
{
c = 255;
}
else
{
c += 128;
}
buffer[yStride + x] = (byte)c;
}
}
}
}
}
}

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

@ -0,0 +1,95 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using ImageSharp.Formats;
using Xunit.Abstractions;
namespace ImageSharp.Tests.Formats.Jpg
{
public class UtilityTestClassBase
{
public UtilityTestClassBase(ITestOutputHelper output)
{
Output = output;
}
protected ITestOutputHelper Output { get; }
// ReSharper disable once InconsistentNaming
public static float[] Create8x8FloatData()
{
float[] result = new float[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
result[i * 8 + j] = i * 10 + j;
}
}
return result;
}
// ReSharper disable once InconsistentNaming
public static int[] Create8x8IntData()
{
int[] result = new int[64];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
result[i * 8 + j] = i * 10 + j;
}
}
return result;
}
internal void Print8x8Data<T>(MutableSpan<T> data) => Print8x8Data(data.Data);
internal void Print8x8Data<T>(T[] data)
{
StringBuilder bld = new StringBuilder();
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
bld.Append($"{data[i * 8 + j],3} ");
}
bld.AppendLine();
}
Output.WriteLine(bld.ToString());
}
internal void PrintLinearData<T>(T[] data) => PrintLinearData(new MutableSpan<T>(data), data.Length);
internal void PrintLinearData<T>(MutableSpan<T> data, int count = -1)
{
if (count < 0) count = data.TotalCount;
StringBuilder bld = new StringBuilder();
for (int i = 0; i < count; i++)
{
bld.Append($"{data[i],3} ");
}
Output.WriteLine(bld.ToString());
}
protected void Measure(int times, Action action, [CallerMemberName] string operationName = null)
{
Output.WriteLine($"{operationName} X {times} ...");
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < times; i++)
{
action();
}
sw.Stop();
Output.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms");
}
}
}

2
tests/ImageSharp.Tests/Formats/Png/PngTests.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using ImageSharp.Formats;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using System.IO; using System.IO;

11
tests/ImageSharp.Tests/TestImages.cs

@ -38,13 +38,18 @@ namespace ImageSharp.Tests
{ {
private static readonly string folder = "TestImages/Formats/Jpg/"; private static readonly string folder = "TestImages/Formats/Jpg/";
public static string Cmyk => folder + "cmyk.jpg"; public static string Cmyk => folder + "cmyk.jpg";
public static string Exif => folder + "exif.jpeg"; public static string Exif => folder + "exif.jpg";
public static string Floorplan => folder + "Floorplan.jpeg"; public static string Floorplan => folder + "Floorplan.jpeg";
public static string Calliphora => folder + "Calliphora.jpg"; public static string Calliphora => folder + "Calliphora.jpg";
public static string Turtle => folder + "turtle.jpg"; public static string Turtle => folder + "turtle.jpg";
public static string Fb => folder + "fb.jpg"; public static string Fb => folder + "fb.jpg";
public static string Progress => folder + "progress.jpg"; public static string Progress => folder + "progress.jpg";
public static string GammaDalaiLamaGray => folder + "gamma_dalai_lama_gray.jpg"; public static string GammaDalaiLamaGray => folder + "gamma_dalai_lama_gray.jpg";
public static readonly string[] All = {
Cmyk, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray
};
} }
public static class Bmp public static class Bmp
@ -56,6 +61,10 @@ namespace ImageSharp.Tests
public static string F => folder + "F.bmp"; public static string F => folder + "F.bmp";
public static string NegHeight => folder + "neg_height.bmp"; public static string NegHeight => folder + "neg_height.bmp";
public static readonly string[] All = {
Car, F, NegHeight
};
} }
public static class Gif public static class Gif

Loading…
Cancel
Save