Browse Source

fix rounding in YCbCr conversion, started refactoring dequantiziation logic

pull/337/head
Anton Firszov 9 years ago
parent
commit
1c84a3c312
  1. 33
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  2. 20
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
  3. 44
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
  4. 4
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs
  5. 4
      src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs
  6. 1
      src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs
  7. 38
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  8. 15
      tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs
  9. 2
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  10. 21
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs
  11. 9
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

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

@ -421,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// </summary> /// </summary>
/// <param name="scaleVec">Vector to multiply by</param> /// <param name="scaleVec">Vector to multiply by</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyAllInplace(float scaleVec) public void MultiplyInplace(float scaleVec)
{ {
this.V0L *= scaleVec; this.V0L *= scaleVec;
this.V0R *= scaleVec; this.V0R *= scaleVec;
@ -441,6 +441,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
this.V7R *= scaleVec; this.V7R *= scaleVec;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyInplace(ref Block8x8F other)
{
this.V0L *= other.V0L;
this.V0R *= other.V0R;
this.V1L *= other.V1L;
this.V1R *= other.V1R;
this.V2L *= other.V2L;
this.V2R *= other.V2R;
this.V3L *= other.V3L;
this.V3R *= other.V3R;
this.V4L *= other.V4L;
this.V4R *= other.V4R;
this.V5L *= other.V5L;
this.V5R *= other.V5R;
this.V6L *= other.V6L;
this.V6R *= other.V6R;
this.V7L *= other.V7L;
this.V7R *= other.V7R;
}
/// <summary> /// <summary>
/// Adds a vector to all elements of the block. /// Adds a vector to all elements of the block.
/// </summary> /// </summary>
@ -472,16 +493,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// <param name="blockPtr">Block pointer</param> /// <param name="blockPtr">Block pointer</param>
/// <param name="qtPtr">Qt pointer</param> /// <param name="qtPtr">Qt pointer</param>
/// <param name="unzigPtr">Unzig pointer</param> /// <param name="unzigPtr">Unzig pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{ {
float* b = (float*)blockPtr; float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr; float* qtp = (float*)qtPtr;
for (int zig = 0; zig < Size; zig++) for (int qtIndex = 0; qtIndex < Size; qtIndex++)
{ {
float* unzigPos = b + unzigPtr[zig]; int blockIndex = unzigPtr[qtIndex];
float* unzigPos = b + blockIndex;
float val = *unzigPos; float val = *unzigPos;
val *= qtp[zig]; val *= qtp[qtIndex];
*unzigPos = val; *unzigPos = val;
} }
} }

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

@ -23,21 +23,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// </summary> /// </summary>
private DataPointers pointers; private DataPointers pointers;
private Size subSamplingDivisors;
/// <summary> /// <summary>
/// Initialize the <see cref="JpegBlockPostProcessor"/> instance on the stack. /// Initialize the <see cref="JpegBlockPostProcessor"/> instance on the stack.
/// </summary> /// </summary>
/// <param name="postProcessor">The <see cref="JpegBlockPostProcessor"/> instance</param> public static void Init(JpegBlockPostProcessor* postProcessor, IRawJpegData decoder, IJpegComponent component)
public static void Init(JpegBlockPostProcessor* postProcessor)
{ {
postProcessor->data = ComputationData.Create(); postProcessor->data = ComputationData.Create();
postProcessor->pointers = new DataPointers(&postProcessor->data); postProcessor->pointers = new DataPointers(&postProcessor->data);
int qtIndex = component.QuantizationTableIndex;
postProcessor->data.QuantiazationTable = decoder.QuantizationTables[qtIndex];
postProcessor->subSamplingDivisors = component.SubSamplingDivisors;
} }
public void QuantizeAndTransform(IRawJpegData decoder, IJpegComponent component, ref Block8x8 sourceBlock) public void QuantizeAndTransform(ref Block8x8 sourceBlock)
{ {
this.data.SourceBlock = sourceBlock.AsFloatBlock(); this.data.SourceBlock = sourceBlock.AsFloatBlock();
int qtIndex = component.QuantizationTableIndex;
this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex];
Block8x8F* b = this.pointers.SourceBlock; Block8x8F* b = this.pointers.SourceBlock;
@ -47,12 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
} }
public void ProcessBlockColorsInto( public void ProcessBlockColorsInto(
IRawJpegData decoder,
IJpegComponent component,
ref Block8x8 sourceBlock, ref Block8x8 sourceBlock,
BufferArea<float> destArea) BufferArea<float> destArea)
{ {
this.QuantizeAndTransform(decoder, component, ref sourceBlock); this.QuantizeAndTransform(ref sourceBlock);
this.data.WorkspaceBlock1.NormalizeColorsInplace(); this.data.WorkspaceBlock1.NormalizeColorsInplace();
@ -61,8 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
// Unfortunately, we need to emulate this to be "more accurate" :( // Unfortunately, we need to emulate this to be "more accurate" :(
this.data.WorkspaceBlock1.RoundInplace(); this.data.WorkspaceBlock1.RoundInplace();
Size divs = component.SubSamplingDivisors; this.data.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height);
this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height);
} }
/// <summary> /// <summary>

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

@ -121,9 +121,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
tmp.MultiplyInplace(1.772F); tmp.MultiplyInplace(1.772F);
b.AddInplace(ref tmp); b.AddInplace(ref tmp);
r.RoundAndDownscale(); if (Vector<float>.Count == 4)
g.RoundAndDownscale(); {
b.RoundAndDownscale(); // TODO: Find a way to properly run & test this path on modern AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?)
r.RoundAndDownscaleBasic();
g.RoundAndDownscaleBasic();
b.RoundAndDownscaleBasic();
}
else if (Vector<float>.Count == 8)
{
r.RoundAndDownscaleAvx2();
g.RoundAndDownscaleAvx2();
b.RoundAndDownscaleAvx2();
}
else
{
// TODO: Run fallback scalar code here
// However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007
throw new NotImplementedException("Your CPU architecture is too modern!");
}
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order: // Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i); ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
@ -145,17 +161,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
private static readonly Vector4 Half = new Vector4(0.5f); private static readonly Vector4 Half = new Vector4(0.5f);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RoundAndDownscale() public void RoundAndDownscaleBasic()
{ {
// Emulate rounding: ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A);
this.A += Half; a = a.FastRound();
this.B += Half;
ref Vector<float> b = ref Unsafe.As<Vector4, Vector<float>>(ref this.B);
b = b.FastRound();
// Downscale by 1/255 // Downscale by 1/255
this.A *= Scale; this.A *= Scale;
this.B *= Scale; this.B *= Scale;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RoundAndDownscaleAvx2()
{
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this);
Vector<float> v = self;
v = v.FastRound();
// Downscale by 1/255
v *= new Vector<float>(1 / 255f);
self = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyInplace(float value) public void MultiplyInplace(float value)
{ {

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

@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
public unsafe void CopyBlocksToColorBuffer() public unsafe void CopyBlocksToColorBuffer()
{ {
var blockPp = default(JpegBlockPostProcessor); var blockPp = default(JpegBlockPostProcessor);
JpegBlockPostProcessor.Init(&blockPp); JpegBlockPostProcessor.Init(&blockPp, this.ImagePostProcessor.RawJpeg, this.Component);
for (int y = 0; y < this.BlockRowsPerStep; y++) for (int y = 0; y < this.BlockRowsPerStep; y++)
{ {
@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
this.blockAreaSize.Width, this.blockAreaSize.Width,
this.blockAreaSize.Height); this.blockAreaSize.Height);
blockPp.ProcessBlockColorsInto(this.ImagePostProcessor.RawJpeg, this.Component, ref block, destArea); blockPp.ProcessBlockColorsInto(ref block, destArea);
} }
} }

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

@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
IDCT8x4_RightPart(ref temp, ref dest); IDCT8x4_RightPart(ref temp, ref dest);
// TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? // TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing?
dest.MultiplyAllInplace(C_0_125); dest.MultiplyInplace(C_0_125);
} }
/// <summary> /// <summary>
@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
FDCT8x4_LeftPart(ref temp, ref dest); FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest); FDCT8x4_RightPart(ref temp, ref dest);
dest.MultiplyAllInplace(C_0_125); dest.MultiplyInplace(C_0_125);
} }
} }
} }

1
src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs

@ -6,7 +6,6 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{ {
/// <summary> /// <summary>
/// TODO: This should be simply just a <see cref="Block8x8"/>!
/// Holds the Jpeg UnZig array in a value/stack type. /// Holds the Jpeg UnZig array in a value/stack type.
/// Unzig maps from the zigzag ordering to the natural ordering. For example, /// 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 /// unzig[3] is the column and row of the fourth element in zigzag order. The

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

@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
float[] data = Create8x8FloatData(); float[] data = Create8x8FloatData();
var block = new Block8x8F(); var block = new Block8x8F();
block.LoadFrom(data); block.LoadFrom(data);
block.MultiplyAllInplace(5); block.MultiplyInplace(5);
int stride = 256; int stride = 256;
int height = 42; int height = 42;
@ -370,5 +370,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
} }
[Fact]
public void MultiplyInplace_ByOtherBlock()
{
Block8x8F original = CreateRandomFloatBlock(-500, 500, 42);
Block8x8F m = CreateRandomFloatBlock(-500, 500, 42);
Block8x8F actual = original;
actual.MultiplyInplace(ref m);
for (int i = 0; i < Block8x8F.Size; i++)
{
Assert.Equal(original[i]*m[i], actual[i]);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public unsafe void DequantizeBlock(int seed)
{
Block8x8F original = CreateRandomFloatBlock(-500, 500, seed);
Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42);
var unzig = UnzigData.Create();
Block8x8F expected = original;
Block8x8F actual = original;
ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data);
Block8x8F.DequantizeBlock(&actual, &qt, unzig.Data);
this.CompareBlocks(expected, actual, 0);
}
} }
} }

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

@ -19,6 +19,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
/// </summary> /// </summary>
internal static partial class ReferenceImplementations internal static partial class ReferenceImplementations
{ {
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
for (int qtIndex = 0; qtIndex < Block8x8F.Size; qtIndex++)
{
int i = unzigPtr[qtIndex];
float* unzigPos = b + i;
float val = *unzigPos;
val *= qtp[qtIndex];
*unzigPos = val;
}
}
/// <summary> /// <summary>
/// Transpose 8x8 block stored linearly in a <see cref="Span{T}"/> (inplace) /// Transpose 8x8 block stored linearly in a <see cref="Span{T}"/> (inplace)
/// </summary> /// </summary>

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

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

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

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

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

@ -109,11 +109,10 @@ namespace SixLabors.ImageSharp.Tests
{ {
this.FilePath = filePath; this.FilePath = filePath;
} }
public FileProvider() /// <summary>
{ /// Gets the file path relative to the "~/tests/images" folder
} /// </summary>
public string FilePath { get; private set; } public string FilePath { get; private set; }
public override string SourceFileOrDescription => this.FilePath; public override string SourceFileOrDescription => this.FilePath;

Loading…
Cancel
Save