From 2a7ce4cd90bbb767e2b1de2a706b11a384b3ff34 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 00:48:33 +0200 Subject: [PATCH 01/12] fix rounding in YCbCr conversion, started refactoring dequantiziation logic --- .../Formats/Jpeg/Common/Block8x8F.cs | 33 +++++++++++--- .../Common/Decoder/JpegBlockPostProcessor.cs | 20 ++++----- .../Decoder/JpegColorConverter.FromYCbCr.cs | 44 ++++++++++++++++--- .../Decoder/JpegComponentPostProcessor.cs | 4 +- .../Jpeg/Common/FastFloatingPointDCT.cs | 4 +- .../Formats/Jpeg/Common/UnzigData.cs | 1 - .../Formats/Jpg/Block8x8FTests.cs | 38 +++++++++++++++- .../Jpg/Utils/ReferenceImplementations.cs | 15 +++++++ .../TestUtilities/ApproximateFloatComparer.cs | 2 +- .../ImageComparison/ImageSimilarityReport.cs | 21 +++++++-- .../ImageProviders/FileProvider.cs | 9 ++-- 11 files changed, 154 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 191fa50ca..3ef498e10 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -421,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Vector to multiply by [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyAllInplace(float scaleVec) + public void MultiplyInplace(float scaleVec) { this.V0L *= scaleVec; this.V0R *= scaleVec; @@ -441,6 +441,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common 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; + } + /// /// Adds a vector to all elements of the block. /// @@ -472,16 +493,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Block pointer /// Qt pointer /// Unzig pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) { float* b = (float*)blockPtr; float* qtp = (float*)qtPtr; - for (int zig = 0; zig < Size; zig++) + for (int qtIndex = 0; qtIndex < Size; qtIndex++) { - float* unzigPos = b + unzigPtr[zig]; + int blockIndex = unzigPtr[qtIndex]; + float* unzigPos = b + blockIndex; + float val = *unzigPos; - val *= qtp[zig]; + val *= qtp[qtIndex]; *unzigPos = val; } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 4c4701753..e679fddcf 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -23,21 +23,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// private DataPointers pointers; + private Size subSamplingDivisors; + /// /// Initialize the instance on the stack. /// - /// The instance - public static void Init(JpegBlockPostProcessor* postProcessor) + public static void Init(JpegBlockPostProcessor* postProcessor, IRawJpegData decoder, IJpegComponent component) { postProcessor->data = ComputationData.Create(); 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(); - int qtIndex = component.QuantizationTableIndex; - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; Block8x8F* b = this.pointers.SourceBlock; @@ -47,12 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder } public void ProcessBlockColorsInto( - IRawJpegData decoder, - IJpegComponent component, ref Block8x8 sourceBlock, BufferArea destArea) { - this.QuantizeAndTransform(decoder, component, ref sourceBlock); + this.QuantizeAndTransform(ref sourceBlock); 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" :( this.data.WorkspaceBlock1.RoundInplace(); - Size divs = component.SubSamplingDivisors; - this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height); + this.data.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs index 059b2e89a..693afc6f2 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs @@ -121,9 +121,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder tmp.MultiplyInplace(1.772F); b.AddInplace(ref tmp); - r.RoundAndDownscale(); - g.RoundAndDownscale(); - b.RoundAndDownscale(); + if (Vector.Count == 4) + { + // TODO: Find a way to properly run & test this path on modern AVX2 PC-s! (Have I already mentioned that Vector is terrible?) + r.RoundAndDownscaleBasic(); + g.RoundAndDownscaleBasic(); + b.RoundAndDownscaleBasic(); + } + else if (Vector.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: 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); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RoundAndDownscale() + public void RoundAndDownscaleBasic() { - // Emulate rounding: - this.A += Half; - this.B += Half; + ref Vector a = ref Unsafe.As>(ref this.A); + a = a.FastRound(); + + ref Vector b = ref Unsafe.As>(ref this.B); + b = b.FastRound(); // Downscale by 1/255 this.A *= Scale; this.B *= Scale; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RoundAndDownscaleAvx2() + { + ref Vector self = ref Unsafe.As>(ref this); + Vector v = self; + v = v.FastRound(); + + // Downscale by 1/255 + v *= new Vector(1 / 255f); + self = v; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MultiplyInplace(float value) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs index feb5164d7..53aafed01 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public unsafe void CopyBlocksToColorBuffer() { var blockPp = default(JpegBlockPostProcessor); - JpegBlockPostProcessor.Init(&blockPp); + JpegBlockPostProcessor.Init(&blockPp, this.ImagePostProcessor.RawJpeg, this.Component); for (int y = 0; y < this.BlockRowsPerStep; y++) { @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder this.blockAreaSize.Width, this.blockAreaSize.Height); - blockPp.ProcessBlockColorsInto(this.ImagePostProcessor.RawJpeg, this.Component, ref block, destArea); + blockPp.ProcessBlockColorsInto(ref block, destArea); } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs index 8a4f56e3d..bdea14793 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common IDCT8x4_RightPart(ref temp, ref dest); // TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? - dest.MultiplyAllInplace(C_0_125); + dest.MultiplyInplace(C_0_125); } /// @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common FDCT8x4_LeftPart(ref temp, ref dest); FDCT8x4_RightPart(ref temp, ref dest); - dest.MultiplyAllInplace(C_0_125); + dest.MultiplyInplace(C_0_125); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs index e243938e3..aaefbb3af 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.Common { /// - /// TODO: This should be simply just a ! /// 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 diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 52c38bee8..59fa19be4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] data = Create8x8FloatData(); var block = new Block8x8F(); block.LoadFrom(data); - block.MultiplyAllInplace(5); + block.MultiplyInplace(5); int stride = 256; int height = 42; @@ -370,5 +370,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual); } } + + [Fact] + public void MultiplyInplace_ByOtherBlock() + { + Block8x8F original = CreateRandomFloatBlock(-500, 500, 42); + Block8x8F m = CreateRandomFloatBlock(-500, 500, 42); + + Block8x8F actual = original; + + actual.MultiplyInplace(ref m); + + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal(original[i]*m[i], actual[i]); + } + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public unsafe void DequantizeBlock(int seed) + { + Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); + Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); + + var unzig = 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); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index c8240bf08..38339e58f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -19,6 +19,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// 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; + } + } + /// /// Transpose 8x8 block stored linearly in a (inplace) /// diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 6b3f6ccd2..70d4df273 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests { float d = x - y; - return d > -this.Eps && d < this.Eps; + return d >= -this.Eps && d <= this.Eps; } public int GetHashCode(float obj) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 78e390bbd..9501a6c88 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -26,9 +26,24 @@ // TODO: This should not be a nullable value! public float? TotalNormalizedDifference { get; } - public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue - ? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%" - : "?"; + public string DifferencePercentageString + { + get + { + if (!this.TotalNormalizedDifference.HasValue) + { + return "?"; + } + else if (this.TotalNormalizedDifference == 0) + { + return "0%"; + } + else + { + return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"; + } + } + } public PixelDifference[] Differences { get; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 8ff532b2f..0a5eb8f61 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -109,11 +109,10 @@ namespace SixLabors.ImageSharp.Tests { this.FilePath = filePath; } - - public FileProvider() - { - } - + + /// + /// Gets the file path relative to the "~/tests/images" folder + /// public string FilePath { get; private set; } public override string SourceFileOrDescription => this.FilePath; From f475e1b658879c1a014f68e2047e58deb06090e4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 01:16:40 +0200 Subject: [PATCH 02/12] improved Jpeg regression testing --- .../Formats/Jpg/JpegColorConverterTests.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.cs | 126 ++++++++++++------ .../ImageProviders/FileProvider.cs | 6 + 3 files changed, 91 insertions(+), 43 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 706fa1e20..0197dd917 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class JpegColorConverterTests { - private const float Precision = 1/255f; + private const float Precision = 0.1f / 255; public static readonly TheoryData CommonConversionData = new TheoryData diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index d8d24224b..e26ab2cc9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -9,6 +9,7 @@ using System; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System.Collections.Generic; using System.IO; using System.Linq; @@ -24,6 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using Xunit; using Xunit.Abstractions; + // TODO: Scatter test cases into multiple test classes public class JpegDecoderTests { public static string[] BaselineTestJpegs = @@ -50,16 +52,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, }; + private static readonly Dictionary CustomToleranceValues = new Dictionary + { + // Baseline: + [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, + [TestImages.Jpeg.Baseline.Bad.ExifUndefType] = 0.011f / 100, + [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + + // Progressive: + [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, + [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, + [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, + [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, + [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, + [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, + }; + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; - // TODO: We should make this comparer less tolerant ... - private static readonly ImageComparer VeryTolerantJpegComparer = - ImageComparer.Tolerant(0.005f, perPixelManhattanThreshold: 4); + private const float BaselineTolerance_Orig = 0.001f / 100; + private const float BaselineTolerance_PdfJs = 0.005f; - // BUG: PDF.js output is wrong on spectral level! - private static readonly ImageComparer PdfJsProgressiveComparer = - ImageComparer.Tolerant(0.015f, perPixelManhattanThreshold: 4); + private const float ProgressiveTolerance_Orig = 0.2f / 100; + private const float ProgressiveTolerance_PdfJs = 1.5f / 100; // PDF.js Progressive output is wrong on spectral level! + private ImageComparer GetImageComparerForOrigDecoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + string file = provider.SourceFileOrDescription; + + if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) + { + tolerance = file.ToLower().Contains("baseline") ? BaselineTolerance_Orig : ProgressiveTolerance_Orig; + } + + return ImageComparer.Tolerant(tolerance); + } public JpegDecoderTests(ITestOutputHelper output) { @@ -71,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static IImageDecoder OrigJpegDecoder => new OrigJpegDecoder(); private static IImageDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder(); - + [Fact] public void ParseStream_BasicPropertiesAreCorrect1_PdfJs() { @@ -84,50 +113,56 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); } } - + public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; + [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg_PdfJs(TestImageProvider provider) + [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)] + public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider, bool useOldDecoder) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(PdfJsJpegDecoder)) + IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder; + using (Image image = provider.GetImage(decoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, ImageComparer.Tolerant(BaselineTolerance_PdfJs), appendPixelTypeToFileName: false); } } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)] - public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider, bool useOldDecoder) + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] + public void DecodeBaselineJpeg_Orig(TestImageProvider provider) where TPixel : struct, IPixel { - IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder; - using (Image image = provider.GetImage(decoder)) + using (Image image = provider.GetImage(OrigJpegDecoder)) { image.DebugSave(provider); - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + this.GetImageComparerForOrigDecoder(provider), + appendPixelTypeToFileName: false); } } - + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg_Orig(TestImageProvider provider) + public void DecodeBaselineJpeg_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(OrigJpegDecoder)) + using (Image image = provider.GetImage(PdfJsJpegDecoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + ImageComparer.Tolerant(BaselineTolerance_PdfJs), + appendPixelTypeToFileName: false); } } @@ -144,37 +179,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg_PdfJs(TestImageProvider provider) + public void DecodeProgressiveJpeg_Orig(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(PdfJsJpegDecoder)) + using (Image image = provider.GetImage(OrigJpegDecoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeProgressiveJpegOutputName; - image.CompareToReferenceOutput(provider, PdfJsProgressiveComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + this.GetImageComparerForOrigDecoder(provider), + appendPixelTypeToFileName: false); } } [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg_Orig(TestImageProvider provider) + public void DecodeProgressiveJpeg_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(OrigJpegDecoder)) + using (Image image = provider.GetImage(PdfJsJpegDecoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeProgressiveJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + ImageComparer.Tolerant(ProgressiveTolerance_PdfJs), + appendPixelTypeToFileName: false); } } - - private float GetDifferenceInPercents(Image image, TestImageProvider provider) + + private string GetDifferenceInPercentageString(Image image, TestImageProvider provider) where TPixel : struct, IPixel { var reportingComparer = ImageComparer.Tolerant(0, 0); - + ImageSimilarityReport report = image.GetReferenceOutputSimilarityReports( provider, reportingComparer, @@ -183,10 +224,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (report != null && report.TotalNormalizedDifference.HasValue) { - return report.TotalNormalizedDifference.Value * 100; + return report.DifferencePercentageString; } - return 0; + return "0%"; } private void CompareJpegDecodersImpl(TestImageProvider provider, string testName) @@ -202,14 +243,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Image image = provider.GetImage(OrigJpegDecoder)) { - double d = this.GetDifferenceInPercents(image, provider); - this.Output.WriteLine($"Difference using ORIGINAL decoder: {d:0.0000}%"); + string d = this.GetDifferenceInPercentageString(image, provider); + + this.Output.WriteLine($"Difference using ORIGINAL decoder: {d}"); } using (Image image = provider.GetImage(PdfJsJpegDecoder)) { - double d = this.GetDifferenceInPercents(image, provider); - this.Output.WriteLine($"Difference using PDFJS decoder: {d:0.0000}%"); + string d = this.GetDifferenceInPercentageString(image, provider); + this.Output.WriteLine($"Difference using PDFJS decoder: {d}"); } } @@ -228,7 +270,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { this.CompareJpegDecodersImpl(provider, DecodeProgressiveJpegOutputName); } - + [Theory] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 75)] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 100)] @@ -256,7 +298,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var mirror = Image.Load(data, OrigJpegDecoder); mirror.DebugSave(provider, $"_{subsample}_Q{quality}"); } - + [Fact] public void Decoder_Reads_Correct_Resolution_From_Jfif() { @@ -314,7 +356,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // into "\tests\Images\ActualOutput\JpegDecoderTests\" //[Theory] //[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] - public void ValidateProgressivePdfJsOutput(TestImageProvider provider, + public void ValidateProgressivePdfJsOutput(TestImageProvider provider, string pdfJsOriginalResultImage) where TPixel : struct, IPixel { @@ -335,7 +377,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult); ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult); - + this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}"); this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}"); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 0a5eb8f61..92332eba0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -105,6 +105,12 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); + // Needed for deserialization! + // ReSharper disable once UnusedMember.Local + public FileProvider() + { + } + public FileProvider(string filePath) { this.FilePath = filePath; From e6ef197bcbc4b9c4d2f6b1bd8ad9b0772ff0cd35 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 01:30:27 +0200 Subject: [PATCH 04/12] FormattableString, why are you so cruel to Eastern Europeans? --- .../TestUtilities/ImagingTestCaseUtility.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 5867eaf85..7fd7a59a9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -89,6 +89,8 @@ namespace SixLabors.ImageSharp.Tests return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}"; } + private static string Inv(FormattableString formattable) => System.FormattableString.Invariant(formattable); + /// /// Gets the recommended file name for the output of the test /// @@ -111,14 +113,16 @@ namespace SixLabors.ImageSharp.Tests TypeInfo info = type.GetTypeInfo(); if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) { - detailsString = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", testOutputDetails); + detailsString = Inv($"{testOutputDetails}"); } else { IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); - detailsString = String.Join( - "_", properties.ToDictionary(x => x.Name, x => string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", x.GetValue(testOutputDetails))).Select(x => $"{x.Key}-{x.Value}") + detailsString = string.Join( + "_", + properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) + .Select(x => Inv($"{x.Key}-{x.Value}")) ); } } From 005a454b40c8f67c1e4c006ddfcbcd0dcc0ab094 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 02:22:13 +0200 Subject: [PATCH 05/12] optimized quantization --- .../Common/Decoder/JpegBlockPostProcessor.cs | 28 ++++++++----------- .../Jpeg/Common/{UnzigData.cs => ZigZag.cs} | 23 ++++++++++++--- .../OrigJpegScanDecoder.ComputationData.cs | 4 +-- .../Jpeg/GolangPort/JpegEncoderCore.cs | 4 +-- .../Formats/Jpg/Block8x8FTests.cs | 26 +++++++++++++++-- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 4 +-- .../Jpg/Utils/ReferenceImplementations.cs | 2 +- 7 files changed, 62 insertions(+), 29 deletions(-) rename src/ImageSharp/Formats/Jpeg/Common/{UnzigData.cs => ZigZag.cs} (70%) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index e679fddcf..7139260ec 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -34,26 +34,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder postProcessor->pointers = new DataPointers(&postProcessor->data); int qtIndex = component.QuantizationTableIndex; - postProcessor->data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + postProcessor->data.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); postProcessor->subSamplingDivisors = component.SubSamplingDivisors; } - public void QuantizeAndTransform(ref Block8x8 sourceBlock) + public void ProcessBlockColorsInto( + ref Block8x8 sourceBlock, + BufferArea destArea) { this.data.SourceBlock = sourceBlock.AsFloatBlock(); Block8x8F* b = this.pointers.SourceBlock; - Block8x8F.DequantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + // Dequantize: + b->MultiplyInplace(ref this.data.DequantiazationTable); FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2); - } - - public void ProcessBlockColorsInto( - ref Block8x8 sourceBlock, - BufferArea destArea) - { - this.QuantizeAndTransform(ref sourceBlock); this.data.WorkspaceBlock1.NormalizeColorsInplace(); @@ -89,12 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// The quantization table as /// - public Block8x8F QuantiazationTable; + public Block8x8F DequantiazationTable; /// /// The jpeg unzig data /// - public UnzigData Unzig; + public ZigZag Unzig; /// /// Creates and initializes a new instance @@ -103,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public static ComputationData Create() { var data = default(ComputationData); - data.Unzig = UnzigData.Create(); + data.Unzig = ZigZag.CreateUnzigTable(); return data; } } @@ -129,9 +125,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public Block8x8F* WorkspaceBlock2; /// - /// Pointer to + /// Pointer to /// - public Block8x8F* QuantiazationTable; + public Block8x8F* DequantiazationTable; /// /// Pointer to as int* @@ -147,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder this.SourceBlock = &dataPtr->SourceBlock; this.WorkspaceBlock1 = &dataPtr->WorkspaceBlock1; this.WorkspaceBlock2 = &dataPtr->WorkspaceBlock2; - this.QuantiazationTable = &dataPtr->QuantiazationTable; + this.DequantiazationTable = &dataPtr->DequantiazationTable; this.Unzig = dataPtr->Unzig.Data; } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs b/src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs similarity index 70% rename from src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs rename to src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs index aaefbb3af..18270f5ba 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// 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). /// - internal unsafe struct UnzigData + internal unsafe struct ZigZag { /// /// Copy of in a value type @@ -32,15 +32,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common }; /// - /// Creates and fills an instance of with Jpeg unzig indices + /// Creates and fills an instance of with Jpeg unzig indices /// /// The new instance - public static UnzigData Create() + public static ZigZag CreateUnzigTable() { - UnzigData result = default(UnzigData); + ZigZag result = default(ZigZag); int* unzigPtr = result.Data; Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); return result; } + + /// + /// Apply Zigging to the given quantization table, so it will be sufficient to multiply blocks for dequantizing them. + /// + public static Block8x8F CreateDequantizationTable(ref Block8x8F qt) + { + Block8x8F result = default(Block8x8F); + + for (int i = 0; i < 64; i++) + { + result[Unzig[i]] = qt[i]; + } + + return result; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs index 6252d8209..d426ed4f1 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The jpeg unzig data /// - public UnzigData Unzig; + public ZigZag Unzig; /// /// The buffer storing the -s for each component @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public static ComputationData Create() { ComputationData data = default(ComputationData); - data.Unzig = UnzigData.Create(); + data.Unzig = ZigZag.CreateUnzigTable(); return data; } } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 984fb828c..3a24bded3 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -455,7 +455,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - UnzigData unzig = UnzigData.Create(); + ZigZag unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; @@ -912,7 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - UnzigData unzig = UnzigData.Create(); + ZigZag unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 59fa19be4..66c9bb3e1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -309,7 +309,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var qt = new Block8x8F(); qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); - var unzig = UnzigData.Create(); + var unzig = ZigZag.CreateUnzigTable(); int* expectedResults = stackalloc int[Block8x8F.Size]; ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data); @@ -396,7 +396,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); - var unzig = UnzigData.Create(); + var unzig = ZigZag.CreateUnzigTable(); Block8x8F expected = original; Block8x8F actual = original; @@ -406,5 +406,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0); } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public unsafe void ZigZag_CreateDequantizationTable_MultiplicationShouldQuantize(int seed) + { + Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); + Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); + + var unzig = ZigZag.CreateUnzigTable(); + Block8x8F zigQt = ZigZag.CreateDequantizationTable(ref qt); + + Block8x8F expected = original; + Block8x8F actual = original; + + ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data); + + actual.MultiplyInplace(ref zigQt); + + this.CompareBlocks(expected, actual, 0); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index 063f42c00..a1ff3550a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -41,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.DecodeJpegBenchmarkImpl(fileName, new OrigJpegDecoder()); } - [Theory] // Benchmark, enable manually - [MemberData(nameof(DecodeJpegData))] + //[Theory] // Benchmark, enable manually + //[MemberData(nameof(DecodeJpegData))] public void DecodeJpeg_PdfJs(string fileName) { this.DecodeJpegBenchmarkImpl(fileName, new PdfJsJpegDecoder()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index 38339e58f..f3722b5d6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// The input block /// The destination block of integers /// The quantization table - /// Pointer to + /// Pointer to public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) { float* s = (float*)src; From c1c49f64af7da2de01b6fe1aa6febefeaa4a4c35 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 02:36:35 +0200 Subject: [PATCH 06/12] inlined stuff in CopyTo2x2() --- .../Formats/Jpeg/Common/Block8x8F.CopyTo.cs | 146 ++++++++++++++++++ .../Formats/Jpeg/Common/Block8x8F.cs | 106 +------------ 2 files changed, 147 insertions(+), 105 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs new file mode 100644 index 000000000..c1c5cfcde --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs @@ -0,0 +1,146 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + internal partial struct Block8x8F + { + /// + /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical. + /// + public void CopyTo(BufferArea area, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + this.CopyTo(area); + return; + } + else if (horizontalScale == 2 && verticalScale == 2) + { + this.CopyTo2x2(area); + return; + } + + // TODO: Optimize: implement all the cases with loopless special code! (T4?) + for (int y = 0; y < 8; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; + + float value = this[y8 + x]; + + for (int i = 0; i < verticalScale; i++) + { + for (int j = 0; j < horizontalScale; j++) + { + area[xx + j, yy + i] = value; + } + } + } + } + } + + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(BufferArea area) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); + int destStride = area.Stride * sizeof(float); + + CopyRowImpl(ref selfBase, ref destBase, destStride, 0); + CopyRowImpl(ref selfBase, ref destBase, destStride, 1); + CopyRowImpl(ref selfBase, ref destBase, destStride, 2); + CopyRowImpl(ref selfBase, ref destBase, destStride, 3); + CopyRowImpl(ref selfBase, ref destBase, destStride, 4); + CopyRowImpl(ref selfBase, ref destBase, destStride, 5); + CopyRowImpl(ref selfBase, ref destBase, destStride, 6); + CopyRowImpl(ref selfBase, ref destBase, destStride, 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) + { + ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); + ref byte d = ref Unsafe.Add(ref destBase, row * destStride); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } + + private void CopyTo2x2(BufferArea area) + { + ref float destBase = ref area.GetReferenceToOrigo(); + int destStride = area.Stride; + + this.WidenCopyImpl2x2(ref destBase, 0, destStride); + this.WidenCopyImpl2x2(ref destBase, 1, destStride); + this.WidenCopyImpl2x2(ref destBase, 2, destStride); + this.WidenCopyImpl2x2(ref destBase, 3, destStride); + this.WidenCopyImpl2x2(ref destBase, 4, destStride); + this.WidenCopyImpl2x2(ref destBase, 5, destStride); + this.WidenCopyImpl2x2(ref destBase, 6, destStride); + this.WidenCopyImpl2x2(ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WidenCopyImpl2x2(ref float destBase, int row, int destStride) + { + ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row); + ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); + ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride); + + Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W; + Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 0) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 1) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 2) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 3) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 4) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 5) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 6) = selfLeft.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 7) = selfRight.W; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl(ref Vector4 s, ref float destBase) + { + Unsafe.Add(ref destBase, 0) = s.X; + Unsafe.Add(ref destBase, 1) = s.X; + Unsafe.Add(ref destBase, 2) = s.Y; + Unsafe.Add(ref destBase, 3) = s.Y; + Unsafe.Add(ref destBase, 4) = s.Z; + Unsafe.Add(ref destBase, 5) = s.Z; + Unsafe.Add(ref destBase, 6) = s.W; + Unsafe.Add(ref destBase, 7) = s.W; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 3ef498e10..aecd5c59e 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -7,7 +7,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.Memory; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Common @@ -306,109 +305,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(BufferArea area) - { - ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); - int destStride = area.Stride * sizeof(float); - - CopyRowImpl(ref selfBase, ref destBase, destStride, 0); - CopyRowImpl(ref selfBase, ref destBase, destStride, 1); - CopyRowImpl(ref selfBase, ref destBase, destStride, 2); - CopyRowImpl(ref selfBase, ref destBase, destStride, 3); - CopyRowImpl(ref selfBase, ref destBase, destStride, 4); - CopyRowImpl(ref selfBase, ref destBase, destStride, 5); - CopyRowImpl(ref selfBase, ref destBase, destStride, 6); - CopyRowImpl(ref selfBase, ref destBase, destStride, 7); - } - - public void CopyTo(BufferArea area, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - this.CopyTo(area); - return; - } - else if (horizontalScale == 2 && verticalScale == 2) - { - this.CopyTo2x2(area); - return; - } - - // TODO: Optimize: implement all the cases with loopless special code! (T4?) - for (int y = 0; y < 8; y++) - { - int yy = y * verticalScale; - int y8 = y * 8; - - for (int x = 0; x < 8; x++) - { - int xx = x * horizontalScale; - - float value = this[y8 + x]; - - for (int i = 0; i < verticalScale; i++) - { - for (int j = 0; j < horizontalScale; j++) - { - area[xx + j, yy + i] = value; - } - } - } - } - } - - private void CopyTo2x2(BufferArea area) - { - ref float destBase = ref area.GetReferenceToOrigo(); - int destStride = area.Stride; - - this.CopyRow2x2Impl(ref destBase, 0, destStride); - this.CopyRow2x2Impl(ref destBase, 1, destStride); - this.CopyRow2x2Impl(ref destBase, 2, destStride); - this.CopyRow2x2Impl(ref destBase, 3, destStride); - this.CopyRow2x2Impl(ref destBase, 4, destStride); - this.CopyRow2x2Impl(ref destBase, 5, destStride); - this.CopyRow2x2Impl(ref destBase, 6, destStride); - this.CopyRow2x2Impl(ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) - { - ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); - ref byte d = ref Unsafe.Add(ref destBase, row * destStride); - Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyRow2x2Impl(ref float destBase, int row, int destStride) - { - ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row); - ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); - ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride); - - Stride2VectorCopyImpl(ref selfLeft, ref destLocalOrigo); - Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, 8)); - - Stride2VectorCopyImpl(ref selfLeft, ref Unsafe.Add(ref destLocalOrigo, destStride)); - Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, destStride + 8)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Stride2VectorCopyImpl(ref Vector4 s, ref float destBase) - { - Unsafe.Add(ref destBase, 0) = s.X; - Unsafe.Add(ref destBase, 1) = s.X; - Unsafe.Add(ref destBase, 2) = s.Y; - Unsafe.Add(ref destBase, 3) = s.Y; - Unsafe.Add(ref destBase, 4) = s.Z; - Unsafe.Add(ref destBase, 5) = s.Z; - Unsafe.Add(ref destBase, 6) = s.W; - Unsafe.Add(ref destBase, 7) = s.W; - } - public float[] ToArray() { float[] result = new float[Size]; @@ -543,7 +439,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Source block /// Destination block /// The quantization table - /// Pointer to elements of + /// Pointer to elements of public static unsafe void UnzigDivRound( Block8x8F* block, Block8x8F* dest, From 58dbfb90cb057c4e60b33f0192df4a7c86e97dc4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 06:39:52 +0200 Subject: [PATCH 07/12] add library level tuples --- src/ImageSharp/Common/Tuples/Tuple8.cs | 251 ++++++++++++++++++ src/ImageSharp/Common/Tuples/Vector4Pair.cs | 73 +++++ .../Formats/Jpeg/Common/Block8x8.cs | 89 ++++++- .../Formats/Jpeg/Common/Block8x8F.cs | 2 +- .../Common/Decoder/JpegBlockPostProcessor.cs | 2 +- .../ImageSharp.Tests/Common/SimdUtilsTests.cs | 10 +- 6 files changed, 414 insertions(+), 13 deletions(-) create mode 100644 src/ImageSharp/Common/Tuples/Tuple8.cs create mode 100644 src/ImageSharp/Common/Tuples/Vector4Pair.cs diff --git a/src/ImageSharp/Common/Tuples/Tuple8.cs b/src/ImageSharp/Common/Tuples/Tuple8.cs new file mode 100644 index 000000000..c0b9a9002 --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Tuple8.cs @@ -0,0 +1,251 @@ +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Common.Tuples +{ + /// + /// Contains value type tuples of 8 elements. + /// TODO: Should T4 this stuff to be DRY + /// + internal static class Tuple8 + { + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] + public struct OfSingle + { + [FieldOffset(0 * sizeof(float))] + public float V0; + + [FieldOffset(1 * sizeof(float))] + public float V1; + + [FieldOffset(2 * sizeof(float))] + public float V2; + + [FieldOffset(3 * sizeof(float))] + public float V3; + + [FieldOffset(4 * sizeof(float))] + public float V4; + + [FieldOffset(5 * sizeof(float))] + public float V5; + + [FieldOffset(6 * sizeof(float))] + public float V6; + + [FieldOffset(7 * sizeof(float))] + public float V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(int))] + public struct OfInt32 + { + [FieldOffset(0 * sizeof(int))] + public int V0; + + [FieldOffset(1 * sizeof(int))] + public int V1; + + [FieldOffset(2 * sizeof(int))] + public int V2; + + [FieldOffset(3 * sizeof(int))] + public int V3; + + [FieldOffset(4 * sizeof(int))] + public int V4; + + [FieldOffset(5 * sizeof(int))] + public int V5; + + [FieldOffset(6 * sizeof(int))] + public int V6; + + [FieldOffset(7 * sizeof(int))] + public int V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] + public struct OfUInt32 + { + [FieldOffset(0 * sizeof(uint))] + public uint V0; + + [FieldOffset(1 * sizeof(uint))] + public uint V1; + + [FieldOffset(2 * sizeof(uint))] + public uint V2; + + [FieldOffset(3 * sizeof(uint))] + public uint V3; + + [FieldOffset(4 * sizeof(uint))] + public uint V4; + + [FieldOffset(5 * sizeof(uint))] + public uint V5; + + [FieldOffset(6 * sizeof(uint))] + public uint V6; + + [FieldOffset(7 * sizeof(uint))] + public uint V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + + public void LoadFrom(ref OfUInt16 i) + { + this.V0 = i.V0; + this.V1 = i.V1; + this.V2 = i.V2; + this.V3 = i.V3; + this.V4 = i.V4; + this.V5 = i.V5; + this.V6 = i.V6; + this.V7 = i.V7; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(ushort))] + public struct OfUInt16 + { + [FieldOffset(0 * sizeof(ushort))] + public ushort V0; + + [FieldOffset(1 * sizeof(ushort))] + public ushort V1; + + [FieldOffset(2 * sizeof(ushort))] + public ushort V2; + + [FieldOffset(3 * sizeof(ushort))] + public ushort V3; + + [FieldOffset(4 * sizeof(ushort))] + public ushort V4; + + [FieldOffset(5 * sizeof(ushort))] + public ushort V5; + + [FieldOffset(6 * sizeof(ushort))] + public ushort V6; + + [FieldOffset(7 * sizeof(ushort))] + public ushort V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(short))] + public struct OfInt16 + { + [FieldOffset(0 * sizeof(short))] + public short V0; + + [FieldOffset(1 * sizeof(short))] + public short V1; + + [FieldOffset(2 * sizeof(short))] + public short V2; + + [FieldOffset(3 * sizeof(short))] + public short V3; + + [FieldOffset(4 * sizeof(short))] + public short V4; + + [FieldOffset(5 * sizeof(short))] + public short V5; + + [FieldOffset(6 * sizeof(short))] + public short V6; + + [FieldOffset(7 * sizeof(short))] + public short V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8)] + public struct OfByte + { + [FieldOffset(0)] + public byte V0; + + [FieldOffset(1)] + public byte V1; + + [FieldOffset(2)] + public byte V2; + + [FieldOffset(3)] + public byte V3; + + [FieldOffset(4)] + public byte V4; + + [FieldOffset(5)] + public byte V5; + + [FieldOffset(6)] + public byte V6; + + [FieldOffset(7)] + public byte V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + + public void LoadFrom(ref OfUInt32 i) + { + this.V0 = (byte)i.V0; + this.V1 = (byte)i.V1; + this.V2 = (byte)i.V2; + this.V3 = (byte)i.V3; + this.V4 = (byte)i.V4; + this.V5 = (byte)i.V5; + this.V6 = (byte)i.V6; + this.V7 = (byte)i.V7; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs new file mode 100644 index 000000000..1be936b30 --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -0,0 +1,73 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Common.Tuples +{ + /// + /// Its faster to process multiple Vector4-s together, so let's pair them! + /// On AVX2 this pair should be convertible to of ! + /// + [StructLayout(LayoutKind.Sequential)] + internal struct Vector4Pair + { + public Vector4 A; + + public Vector4 B; + + private static readonly Vector4 Scale = new Vector4(1 / 255f); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MultiplyInplace(float value) + { + this.A *= value; + this.B *= value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddInplace(Vector4 value) + { + this.A += value; + this.B += value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddInplace(ref Vector4Pair other) + { + this.A += other.A; + this.B += other.B; + } + + /// + /// Color-conversion specific downscale method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RoundAndDownscaleBasic() + { + ref Vector a = ref Unsafe.As>(ref this.A); + a = a.FastRound(); + + ref Vector b = ref Unsafe.As>(ref this.B); + b = b.FastRound(); + + // Downscale by 1/255 + this.A *= Scale; + this.B *= Scale; + } + + /// + /// AVX2-only color-conversion specific downscale method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RoundAndDownscaleAvx2() + { + ref Vector self = ref Unsafe.As>(ref this); + Vector v = self; + v = v.FastRound(); + + // Downscale by 1/255 + v *= new Vector(1 / 255f); + self = v; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs index 3f4c69c3e..1291f160a 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -176,17 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } /// - /// Convert into + /// Convert to /// public Block8x8F AsFloatBlock() { - // TODO: Optimize this var result = default(Block8x8F); - for (int i = 0; i < Size; i++) - { - result[i] = this[i]; - } - + this.CopyToFloatBlock(ref result); return result; } @@ -302,5 +297,85 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } + + /// + /// Convert values into as -s + /// + public void CopyToFloatBlock(ref Block8x8F dest) + { + ref short selfRef = ref Unsafe.As(ref this); + + dest.V0L.X = Unsafe.Add(ref selfRef, 0); + dest.V0L.Y = Unsafe.Add(ref selfRef, 1); + dest.V0L.Z = Unsafe.Add(ref selfRef, 2); + dest.V0L.W = Unsafe.Add(ref selfRef, 3); + dest.V0R.X = Unsafe.Add(ref selfRef, 4); + dest.V0R.Y = Unsafe.Add(ref selfRef, 5); + dest.V0R.Z = Unsafe.Add(ref selfRef, 6); + dest.V0R.W = Unsafe.Add(ref selfRef, 7); + + dest.V1L.X = Unsafe.Add(ref selfRef, 8); + dest.V1L.Y = Unsafe.Add(ref selfRef, 9); + dest.V1L.Z = Unsafe.Add(ref selfRef, 10); + dest.V1L.W = Unsafe.Add(ref selfRef, 11); + dest.V1R.X = Unsafe.Add(ref selfRef, 12); + dest.V1R.Y = Unsafe.Add(ref selfRef, 13); + dest.V1R.Z = Unsafe.Add(ref selfRef, 14); + dest.V1R.W = Unsafe.Add(ref selfRef, 15); + + dest.V2L.X = Unsafe.Add(ref selfRef, 16); + dest.V2L.Y = Unsafe.Add(ref selfRef, 17); + dest.V2L.Z = Unsafe.Add(ref selfRef, 18); + dest.V2L.W = Unsafe.Add(ref selfRef, 19); + dest.V2R.X = Unsafe.Add(ref selfRef, 20); + dest.V2R.Y = Unsafe.Add(ref selfRef, 21); + dest.V2R.Z = Unsafe.Add(ref selfRef, 22); + dest.V2R.W = Unsafe.Add(ref selfRef, 23); + + dest.V3L.X = Unsafe.Add(ref selfRef, 24); + dest.V3L.Y = Unsafe.Add(ref selfRef, 25); + dest.V3L.Z = Unsafe.Add(ref selfRef, 26); + dest.V3L.W = Unsafe.Add(ref selfRef, 27); + dest.V3R.X = Unsafe.Add(ref selfRef, 28); + dest.V3R.Y = Unsafe.Add(ref selfRef, 29); + dest.V3R.Z = Unsafe.Add(ref selfRef, 30); + dest.V3R.W = Unsafe.Add(ref selfRef, 31); + + dest.V4L.X = Unsafe.Add(ref selfRef, 32); + dest.V4L.Y = Unsafe.Add(ref selfRef, 33); + dest.V4L.Z = Unsafe.Add(ref selfRef, 34); + dest.V4L.W = Unsafe.Add(ref selfRef, 35); + dest.V4R.X = Unsafe.Add(ref selfRef, 36); + dest.V4R.Y = Unsafe.Add(ref selfRef, 37); + dest.V4R.Z = Unsafe.Add(ref selfRef, 38); + dest.V4R.W = Unsafe.Add(ref selfRef, 39); + + dest.V5L.X = Unsafe.Add(ref selfRef, 40); + dest.V5L.Y = Unsafe.Add(ref selfRef, 41); + dest.V5L.Z = Unsafe.Add(ref selfRef, 42); + dest.V5L.W = Unsafe.Add(ref selfRef, 43); + dest.V5R.X = Unsafe.Add(ref selfRef, 44); + dest.V5R.Y = Unsafe.Add(ref selfRef, 45); + dest.V5R.Z = Unsafe.Add(ref selfRef, 46); + dest.V5R.W = Unsafe.Add(ref selfRef, 47); + + dest.V6L.X = Unsafe.Add(ref selfRef, 48); + dest.V6L.Y = Unsafe.Add(ref selfRef, 49); + dest.V6L.Z = Unsafe.Add(ref selfRef, 50); + dest.V6L.W = Unsafe.Add(ref selfRef, 51); + dest.V6R.X = Unsafe.Add(ref selfRef, 52); + dest.V6R.Y = Unsafe.Add(ref selfRef, 53); + dest.V6R.Z = Unsafe.Add(ref selfRef, 54); + dest.V6R.W = Unsafe.Add(ref selfRef, 55); + + dest.V7L.X = Unsafe.Add(ref selfRef, 56); + dest.V7L.Y = Unsafe.Add(ref selfRef, 57); + dest.V7L.Z = Unsafe.Add(ref selfRef, 58); + dest.V7L.W = Unsafe.Add(ref selfRef, 59); + dest.V7R.X = Unsafe.Add(ref selfRef, 60); + dest.V7R.Y = Unsafe.Add(ref selfRef, 61); + dest.V7R.Z = Unsafe.Add(ref selfRef, 62); + dest.V7R.W = Unsafe.Add(ref selfRef, 63); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index aecd5c59e..4ffb63480 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -556,7 +556,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } - private void RoundInplaceSlow() + internal void RoundInplaceSlow() { for (int i = 0; i < Size; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 7139260ec..2db869de7 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder ref Block8x8 sourceBlock, BufferArea destArea) { - this.data.SourceBlock = sourceBlock.AsFloatBlock(); + sourceBlock.CopyToFloatBlock(ref this.data.SourceBlock); Block8x8F* b = this.pointers.SourceBlock; diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index e5f2fd5e7..15a794144 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -8,6 +8,8 @@ namespace SixLabors.ImageSharp.Tests.Common using System.Linq; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Common.Tuples; + using Xunit.Abstractions; using Xunit.Sdk; @@ -243,15 +245,15 @@ namespace SixLabors.ImageSharp.Tests.Common x = (x * scale) + magick; - SimdUtils.Octet.OfUInt32 ii = default(SimdUtils.Octet.OfUInt32); + Tuple8.OfUInt32 ii = default(Tuple8.OfUInt32); - ref Vector iiRef = ref Unsafe.As>(ref ii); + ref Vector iiRef = ref Unsafe.As>(ref ii); iiRef = x; - //SimdUtils.Octet.OfUInt32 ii = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x); + //Tuple8.OfUInt32 ii = Unsafe.As, Tuple8.OfUInt32>(ref x); - ref SimdUtils.Octet.OfByte d = ref dest.NonPortableCast()[0]; + ref Tuple8.OfByte d = ref dest.NonPortableCast()[0]; d.LoadFrom(ref ii); this.Output.WriteLine(ii.ToString()); From 57ccde4521cc87cf058d5eefb1b0a2dff749e70d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 07:30:54 +0200 Subject: [PATCH 08/12] NormalizeColorsAndRoundAvx2() + JpegBlockPostProcessor cleanup --- .../Formats/Jpeg/Common/Block8x8F.cs | 60 ++++---- .../Common/Decoder/JpegBlockPostProcessor.cs | 129 ++++-------------- .../Decoder/JpegColorConverter.FromYCbCr.cs | 4 +- .../Jpeg/Common/FastFloatingPointDCT.cs | 3 + .../Formats/Jpg/Block8x8FTests.cs | 26 +++- 5 files changed, 87 insertions(+), 135 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 4ffb63480..6ba69bec4 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -529,34 +529,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - public void RoundInplace() + public void NormalizeColorsAndRoundInplaceAvx2() + { + Vector off = new Vector(128f); + Vector max = new Vector(255F); + + ref Vector row0 = ref Unsafe.As>(ref this.V0L); + row0 = NormalizeAndRound(row0, off, max); + ref Vector row1 = ref Unsafe.As>(ref this.V1L); + row1 = NormalizeAndRound(row1, off, max); + ref Vector row2 = ref Unsafe.As>(ref this.V2L); + row2 = NormalizeAndRound(row2, off, max); + ref Vector row3 = ref Unsafe.As>(ref this.V3L); + row3 = NormalizeAndRound(row3, off, max); + ref Vector row4 = ref Unsafe.As>(ref this.V4L); + row4 = NormalizeAndRound(row4, off, max); + ref Vector row5 = ref Unsafe.As>(ref this.V5L); + row5 = NormalizeAndRound(row5, off, max); + ref Vector row6 = ref Unsafe.As>(ref this.V6L); + row6 = NormalizeAndRound(row6, off, max); + ref Vector row7 = ref Unsafe.As>(ref this.V7L); + row7 = NormalizeAndRound(row7, off, max); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) { - if (Vector.Count == 8 && Vector.Count == 8) - { - ref Vector row0 = ref Unsafe.As>(ref this.V0L); - row0 = row0.FastRound(); - ref Vector row1 = ref Unsafe.As>(ref this.V1L); - row1 = row1.FastRound(); - ref Vector row2 = ref Unsafe.As>(ref this.V2L); - row2 = row2.FastRound(); - ref Vector row3 = ref Unsafe.As>(ref this.V3L); - row3 = row3.FastRound(); - ref Vector row4 = ref Unsafe.As>(ref this.V4L); - row4 = row4.FastRound(); - ref Vector row5 = ref Unsafe.As>(ref this.V5L); - row5 = row5.FastRound(); - ref Vector row6 = ref Unsafe.As>(ref this.V6L); - row6 = row6.FastRound(); - ref Vector row7 = ref Unsafe.As>(ref this.V7L); - row7 = row7.FastRound(); - } - else - { - this.RoundInplaceSlow(); - } + row += off; + row = Vector.Max(row, Vector.Zero); + row = Vector.Min(row, max); + return row.FastRound(); } - internal void RoundInplaceSlow() + public void RoundInplace() { for (int i = 0; i < Size; i++) { @@ -598,10 +603,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); } - - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] - private struct Row - { - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 2db869de7..0459a5df0 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -14,14 +14,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder internal unsafe struct JpegBlockPostProcessor { /// - /// The + /// Source block /// - private ComputationData data; + public Block8x8F SourceBlock; /// - /// Pointers to elements of + /// Temporal block 1 to store intermediate and/or final computation results /// - private DataPointers pointers; + public Block8x8F WorkspaceBlock1; + + /// + /// Temporal block 2 to store intermediate and/or final computation results + /// + public Block8x8F WorkspaceBlock2; + + /// + /// The quantization table as + /// + public Block8x8F DequantiazationTable; private Size subSamplingDivisors; @@ -30,11 +40,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// public static void Init(JpegBlockPostProcessor* postProcessor, IRawJpegData decoder, IJpegComponent component) { - postProcessor->data = ComputationData.Create(); - postProcessor->pointers = new DataPointers(&postProcessor->data); - int qtIndex = component.QuantizationTableIndex; - postProcessor->data.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); + postProcessor->DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); postProcessor->subSamplingDivisors = component.SubSamplingDivisors; } @@ -42,110 +49,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder ref Block8x8 sourceBlock, BufferArea destArea) { - sourceBlock.CopyToFloatBlock(ref this.data.SourceBlock); - - Block8x8F* b = this.pointers.SourceBlock; + ref Block8x8F b = ref this.SourceBlock; + sourceBlock.CopyToFloatBlock(ref b); // Dequantize: - b->MultiplyInplace(ref this.data.DequantiazationTable); + b.MultiplyInplace(ref this.DequantiazationTable); - FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2); - - this.data.WorkspaceBlock1.NormalizeColorsInplace(); + FastFloatingPointDCT.TransformIDCT(ref b, ref this.WorkspaceBlock1, ref this.WorkspaceBlock2); // To conform better to libjpeg we actually NEED TO loose precision here. // This is because they store blocks as Int16 between all the operations. - // Unfortunately, we need to emulate this to be "more accurate" :( - this.data.WorkspaceBlock1.RoundInplace(); - - this.data.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); - } - - /// - /// Holds the "large" data blocks needed for computations. - /// - [StructLayout(LayoutKind.Sequential)] - public struct ComputationData - { - /// - /// Source block - /// - public Block8x8F SourceBlock; - - /// - /// Temporal block 1 to store intermediate and/or final computation results - /// - public Block8x8F WorkspaceBlock1; - - /// - /// Temporal block 2 to store intermediate and/or final computation results - /// - public Block8x8F WorkspaceBlock2; - - /// - /// The quantization table as - /// - public Block8x8F DequantiazationTable; - - /// - /// The jpeg unzig data - /// - public ZigZag Unzig; - - /// - /// Creates and initializes a new instance - /// - /// The - public static ComputationData Create() + // To be "more accurate", we need to emulate this by rounding! + if (SimdUtils.IsAvx2CompatibleArchitecture) { - var data = default(ComputationData); - data.Unzig = ZigZag.CreateUnzigTable(); - return data; + this.WorkspaceBlock1.NormalizeColorsAndRoundInplaceAvx2(); } - } - - /// - /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of - /// - public struct DataPointers - { - /// - /// Pointer to - /// - public Block8x8F* SourceBlock; - - /// - /// Pointer to - /// - public Block8x8F* WorkspaceBlock1; - - /// - /// Pointer to - /// - public Block8x8F* WorkspaceBlock2; - - /// - /// Pointer to - /// - public Block8x8F* DequantiazationTable; - - /// - /// Pointer to as int* - /// - public int* Unzig; - - /// - /// Initializes a new instance of the struct. - /// - /// Pointer to - internal DataPointers(ComputationData* dataPtr) + else { - this.SourceBlock = &dataPtr->SourceBlock; - this.WorkspaceBlock1 = &dataPtr->WorkspaceBlock1; - this.WorkspaceBlock2 = &dataPtr->WorkspaceBlock2; - this.DequantiazationTable = &dataPtr->DequantiazationTable; - this.Unzig = dataPtr->Unzig.Data; + this.WorkspaceBlock1.NormalizeColorsInplace(); + this.WorkspaceBlock1.RoundInplace(); } + + this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs index 693afc6f2..fef6d6438 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs @@ -123,12 +123,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder if (Vector.Count == 4) { - // TODO: Find a way to properly run & test this path on modern AVX2 PC-s! (Have I already mentioned that Vector is terrible?) + // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?) r.RoundAndDownscaleBasic(); g.RoundAndDownscaleBasic(); b.RoundAndDownscaleBasic(); } - else if (Vector.Count == 8) + else if (SimdUtils.IsAvx2CompatibleArchitecture) { r.RoundAndDownscaleAvx2(); g.RoundAndDownscaleAvx2(); diff --git a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs index bdea14793..3ee6e72c5 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs @@ -50,7 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Temporary block provided by the caller public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) { + // TODO: Transpose is a bottleneck now. We need full AVX support to optimize it: + // https://github.com/dotnet/corefx/issues/22940 src.TransposeInto(ref temp); + IDCT8x4_LeftPart(ref temp, ref dest); IDCT8x4_RightPart(ref temp, ref dest); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 66c9bb3e1..1398b2f89 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -297,6 +297,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } + [Theory] + [InlineData(1)] + [InlineData(2)] + public void NormalizeColorsAndRoundAvx2(int seed) + { + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + this.Output.WriteLine("AVX2 not supported, skipping!"); + return; + } + + Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); + + Block8x8F expected = source; + expected.NormalizeColorsInplace(); + expected.RoundInplace(); + + Block8x8F actual = source; + actual.NormalizeColorsAndRoundInplaceAvx2(); + + this.Output.WriteLine(expected.ToString()); + this.Output.WriteLine(actual.ToString()); + this.CompareBlocks(expected, actual, 0); + } [Theory] [InlineData(1)] @@ -352,7 +376,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void RoundInplace(int seed) + public void RoundInplaceSlow(int seed) { Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); From 8691efabf7a31e9e5466c55fee6879e84dce57f9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 14:02:53 +0200 Subject: [PATCH 09/12] deleting unused tuples for now cuz they are neither tested nor DRY --- src/ImageSharp/Common/Tuples/Tuple8.cs | 156 ------------------------- 1 file changed, 156 deletions(-) diff --git a/src/ImageSharp/Common/Tuples/Tuple8.cs b/src/ImageSharp/Common/Tuples/Tuple8.cs index c0b9a9002..590a3f5c9 100644 --- a/src/ImageSharp/Common/Tuples/Tuple8.cs +++ b/src/ImageSharp/Common/Tuples/Tuple8.cs @@ -8,78 +8,6 @@ namespace SixLabors.ImageSharp.Common.Tuples /// internal static class Tuple8 { - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] - public struct OfSingle - { - [FieldOffset(0 * sizeof(float))] - public float V0; - - [FieldOffset(1 * sizeof(float))] - public float V1; - - [FieldOffset(2 * sizeof(float))] - public float V2; - - [FieldOffset(3 * sizeof(float))] - public float V3; - - [FieldOffset(4 * sizeof(float))] - public float V4; - - [FieldOffset(5 * sizeof(float))] - public float V5; - - [FieldOffset(6 * sizeof(float))] - public float V6; - - [FieldOffset(7 * sizeof(float))] - public float V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - } - - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(int))] - public struct OfInt32 - { - [FieldOffset(0 * sizeof(int))] - public int V0; - - [FieldOffset(1 * sizeof(int))] - public int V1; - - [FieldOffset(2 * sizeof(int))] - public int V2; - - [FieldOffset(3 * sizeof(int))] - public int V3; - - [FieldOffset(4 * sizeof(int))] - public int V4; - - [FieldOffset(5 * sizeof(int))] - public int V5; - - [FieldOffset(6 * sizeof(int))] - public int V6; - - [FieldOffset(7 * sizeof(int))] - public int V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - } - /// /// Value type tuple of 8 -s /// @@ -114,90 +42,6 @@ namespace SixLabors.ImageSharp.Common.Tuples { return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; } - - public void LoadFrom(ref OfUInt16 i) - { - this.V0 = i.V0; - this.V1 = i.V1; - this.V2 = i.V2; - this.V3 = i.V3; - this.V4 = i.V4; - this.V5 = i.V5; - this.V6 = i.V6; - this.V7 = i.V7; - } - } - - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(ushort))] - public struct OfUInt16 - { - [FieldOffset(0 * sizeof(ushort))] - public ushort V0; - - [FieldOffset(1 * sizeof(ushort))] - public ushort V1; - - [FieldOffset(2 * sizeof(ushort))] - public ushort V2; - - [FieldOffset(3 * sizeof(ushort))] - public ushort V3; - - [FieldOffset(4 * sizeof(ushort))] - public ushort V4; - - [FieldOffset(5 * sizeof(ushort))] - public ushort V5; - - [FieldOffset(6 * sizeof(ushort))] - public ushort V6; - - [FieldOffset(7 * sizeof(ushort))] - public ushort V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - } - - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(short))] - public struct OfInt16 - { - [FieldOffset(0 * sizeof(short))] - public short V0; - - [FieldOffset(1 * sizeof(short))] - public short V1; - - [FieldOffset(2 * sizeof(short))] - public short V2; - - [FieldOffset(3 * sizeof(short))] - public short V3; - - [FieldOffset(4 * sizeof(short))] - public short V4; - - [FieldOffset(5 * sizeof(short))] - public short V5; - - [FieldOffset(6 * sizeof(short))] - public short V6; - - [FieldOffset(7 * sizeof(short))] - public short V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } } /// From 8b9a369504c082ce62fb89222400c7216e25355e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 16:26:18 +0200 Subject: [PATCH 10/12] cleanup and docs --- src/ImageSharp/Common/Tuples/Tuple8.cs | 5 +- src/ImageSharp/Common/Tuples/Vector4Pair.cs | 8 +- .../Jpeg/Common/Block8x8F.Generated.cs | 89 +++++++++--------- .../Jpeg/Common/Block8x8F.Generated.tt | 23 ++++- .../Formats/Jpeg/Common/Block8x8F.cs | 91 +++++-------------- .../Common/Decoder/JpegBlockPostProcessor.cs | 22 ++++- .../Decoder/JpegComponentPostProcessor.cs | 5 +- .../Formats/Jpg/Block8x8FTests.cs | 47 +--------- 8 files changed, 122 insertions(+), 168 deletions(-) diff --git a/src/ImageSharp/Common/Tuples/Tuple8.cs b/src/ImageSharp/Common/Tuples/Tuple8.cs index 590a3f5c9..3335e6e37 100644 --- a/src/ImageSharp/Common/Tuples/Tuple8.cs +++ b/src/ImageSharp/Common/Tuples/Tuple8.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Common.Tuples { /// /// Contains value type tuples of 8 elements. - /// TODO: Should T4 this stuff to be DRY + /// TODO: We should T4 this stuff to be DRY /// internal static class Tuple8 { @@ -79,6 +79,9 @@ namespace SixLabors.ImageSharp.Common.Tuples return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; } + /// + /// Sets the values of this tuple by casting all elements of the given tuple to . + /// public void LoadFrom(ref OfUInt32 i) { this.V0 = (byte)i.V0; diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs index 1be936b30..4f43c9811 100644 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -39,10 +39,11 @@ namespace SixLabors.ImageSharp.Common.Tuples } /// - /// Color-conversion specific downscale method. + /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! + /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleBasic() + internal void RoundAndDownscalePreAvx2() { ref Vector a = ref Unsafe.As>(ref this.A); a = a.FastRound(); @@ -56,7 +57,8 @@ namespace SixLabors.ImageSharp.Common.Tuples } /// - /// AVX2-only color-conversion specific downscale method. + /// AVX2-only Downscale method, specific to Jpeg color conversion. + /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void RoundAndDownscaleAvx2() diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index 4c1b4f4d1..e8614205c 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -96,51 +96,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInto(ref Block8x8F d) + internal void NormalizeColorsInplace() { - d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); - d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); - d.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4); - d.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4); - d.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4); - d.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4); - d.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4); - d.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4); - d.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4); - d.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4); - d.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4); - d.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4); - d.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4); - d.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4); - d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); - d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); + this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); + this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); + this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4); + this.V1R = Vector4.Clamp(this.V1R + COff4, CMin4, CMax4); + this.V2L = Vector4.Clamp(this.V2L + COff4, CMin4, CMax4); + this.V2R = Vector4.Clamp(this.V2R + COff4, CMin4, CMax4); + this.V3L = Vector4.Clamp(this.V3L + COff4, CMin4, CMax4); + this.V3R = Vector4.Clamp(this.V3R + COff4, CMin4, CMax4); + this.V4L = Vector4.Clamp(this.V4L + COff4, CMin4, CMax4); + this.V4R = Vector4.Clamp(this.V4R + COff4, CMin4, CMax4); + this.V5L = Vector4.Clamp(this.V5L + COff4, CMin4, CMax4); + this.V5R = Vector4.Clamp(this.V5R + COff4, CMin4, CMax4); + this.V6L = Vector4.Clamp(this.V6L + COff4, CMin4, CMax4); + this.V6R = Vector4.Clamp(this.V6R + COff4, CMin4, CMax4); + this.V7L = Vector4.Clamp(this.V7L + COff4, CMin4, CMax4); + this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4); } - - /// - /// Level shift by +128, clip to [0, 255] - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInplace() + public void NormalizeColorsAndRoundInplaceAvx2() { - this.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); - this.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); - this.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4); - this.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4); - this.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4); - this.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4); - this.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4); - this.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4); - this.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4); - this.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4); - this.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4); - this.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4); - this.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4); - this.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4); - this.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); - this.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); - } - } + Vector off = new Vector(128f); + Vector max = new Vector(255F); + + + ref Vector row0 = ref Unsafe.As>(ref this.V0L); + row0 = NormalizeAndRound(row0, off, max); + + ref Vector row1 = ref Unsafe.As>(ref this.V1L); + row1 = NormalizeAndRound(row1, off, max); + + ref Vector row2 = ref Unsafe.As>(ref this.V2L); + row2 = NormalizeAndRound(row2, off, max); + + ref Vector row3 = ref Unsafe.As>(ref this.V3L); + row3 = NormalizeAndRound(row3, off, max); + + ref Vector row4 = ref Unsafe.As>(ref this.V4L); + row4 = NormalizeAndRound(row4, off, max); + + ref Vector row5 = ref Unsafe.As>(ref this.V5L); + row5 = NormalizeAndRound(row5, off, max); + + ref Vector row6 = ref Unsafe.As>(ref this.V6L); + row6 = NormalizeAndRound(row6, off, max); + + ref Vector row7 = ref Unsafe.As>(ref this.V7L); + row7 = NormalizeAndRound(row7, off, max); + } + } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt index bef3e4914..9ab3ee12c 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt @@ -61,9 +61,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) + internal void NormalizeColorsInplace() { <# @@ -74,11 +73,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common for (int j = 0; j < 2; j++) { char side = j == 0 ? 'L' : 'R'; - Write($"d.V{i}{side} = Vector4.Clamp(V{i}{side} + COff4, CMin4, CMax4);\r\n"); + Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); } } PopIndent(); #> } + + public void NormalizeColorsAndRoundInplaceAvx2() + { + Vector off = new Vector(128f); + Vector max = new Vector(255F); + + <# + + for (int i = 0; i < 8; i++) + { + #> + + ref Vector row<#=i#> = ref Unsafe.As>(ref this.V<#=i#>L); + row<#=i#> = NormalizeAndRound(row<#=i#>, off, max); + <# + } + #> + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 6ba69bec4..ff9db6ab9 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -315,28 +315,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Multiply all elements of the block. /// - /// Vector to multiply by + /// The value to multiply by [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyInplace(float scaleVec) - { - this.V0L *= scaleVec; - this.V0R *= scaleVec; - this.V1L *= scaleVec; - this.V1R *= scaleVec; - this.V2L *= scaleVec; - this.V2R *= scaleVec; - this.V3L *= scaleVec; - this.V3R *= scaleVec; - this.V4L *= scaleVec; - this.V4R *= scaleVec; - this.V5L *= scaleVec; - this.V5R *= scaleVec; - this.V6L *= scaleVec; - this.V6R *= scaleVec; - this.V7L *= scaleVec; - this.V7R *= scaleVec; + public void MultiplyInplace(float value) + { + this.V0L *= value; + this.V0R *= value; + this.V1L *= value; + this.V1R *= value; + this.V2L *= value; + this.V2R *= value; + this.V3L *= value; + this.V3R *= value; + this.V4L *= value; + this.V4R *= value; + this.V5L *= value; + this.V5R *= value; + this.V6L *= value; + this.V6R *= value; + this.V7L *= value; + this.V7R *= value; } + /// + /// Multiply all elements of the block by the corresponding elements of 'other' + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MultiplyInplace(ref Block8x8F other) { @@ -405,33 +408,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } - /// - /// Level shift by +128, clip to [0, 255], and write to buffer. - /// - /// Color buffer - /// Stride offset - /// Temp Block pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void CopyColorsTo(Span destinationBuffer, int stride, Block8x8F* tempBlockPtr) - { - this.NormalizeColorsInto(ref *tempBlockPtr); - ref byte d = ref destinationBuffer.DangerousGetPinnableReference(); - float* src = (float*)tempBlockPtr; - for (int i = 0; i < 8; i++) - { - ref byte dRow = ref Unsafe.Add(ref d, i * stride); - Unsafe.Add(ref dRow, 0) = (byte)src[0]; - Unsafe.Add(ref dRow, 1) = (byte)src[1]; - Unsafe.Add(ref dRow, 2) = (byte)src[2]; - Unsafe.Add(ref dRow, 3) = (byte)src[3]; - Unsafe.Add(ref dRow, 4) = (byte)src[4]; - Unsafe.Add(ref dRow, 5) = (byte)src[5]; - Unsafe.Add(ref dRow, 6) = (byte)src[6]; - Unsafe.Add(ref dRow, 7) = (byte)src[7]; - src += 8; - } - } - /// /// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values. /// To finish the rounding it's enough to (int)-cast these values. @@ -529,29 +505,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - public void NormalizeColorsAndRoundInplaceAvx2() - { - Vector off = new Vector(128f); - Vector max = new Vector(255F); - - ref Vector row0 = ref Unsafe.As>(ref this.V0L); - row0 = NormalizeAndRound(row0, off, max); - ref Vector row1 = ref Unsafe.As>(ref this.V1L); - row1 = NormalizeAndRound(row1, off, max); - ref Vector row2 = ref Unsafe.As>(ref this.V2L); - row2 = NormalizeAndRound(row2, off, max); - ref Vector row3 = ref Unsafe.As>(ref this.V3L); - row3 = NormalizeAndRound(row3, off, max); - ref Vector row4 = ref Unsafe.As>(ref this.V4L); - row4 = NormalizeAndRound(row4, off, max); - ref Vector row5 = ref Unsafe.As>(ref this.V5L); - row5 = NormalizeAndRound(row5, off, max); - ref Vector row6 = ref Unsafe.As>(ref this.V6L); - row6 = NormalizeAndRound(row6, off, max); - ref Vector row7 = ref Unsafe.As>(ref this.V7L); - row7 = NormalizeAndRound(row7, off, max); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 0459a5df0..d0f2b8817 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -33,18 +33,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// public Block8x8F DequantiazationTable; + /// + /// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block. + /// private Size subSamplingDivisors; /// - /// Initialize the instance on the stack. + /// Initializes a new instance of the struct. /// - public static void Init(JpegBlockPostProcessor* postProcessor, IRawJpegData decoder, IJpegComponent component) + public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) { int qtIndex = component.QuantizationTableIndex; - postProcessor->DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); - postProcessor->subSamplingDivisors = component.SubSamplingDivisors; + this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); + this.subSamplingDivisors = component.SubSamplingDivisors; + + this.SourceBlock = default(Block8x8F); + this.WorkspaceBlock1 = default(Block8x8F); + this.WorkspaceBlock2 = default(Block8x8F); } + /// + /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values: + /// - Dequantize + /// - Applying IDCT + /// - Level shift by +128, clip to [0, 255] + /// - Copy the resultin color values into 'destArea' scaling up the block by amount defined in + /// public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, BufferArea destArea) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs index 53aafed01..87c1431e0 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs @@ -66,10 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Invoke for block rows, copy the result into . /// - public unsafe void CopyBlocksToColorBuffer() + public void CopyBlocksToColorBuffer() { - var blockPp = default(JpegBlockPostProcessor); - JpegBlockPostProcessor.Init(&blockPp, this.ImagePostProcessor.RawJpeg, this.Component); + var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); for (int y = 0; y < this.BlockRowsPerStep; y++) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 1398b2f89..8d1e585db 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -222,33 +222,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); } - [Fact] - public unsafe void CopyColorsTo() - { - float[] data = Create8x8FloatData(); - var block = new Block8x8F(); - block.LoadFrom(data); - block.MultiplyInplace(5); - - int stride = 256; - int height = 42; - int offset = height * 10 + 20; - - byte[] colorsExpected = new byte[stride * height]; - byte[] colorsActual = new byte[stride * height]; - - var temp = new Block8x8F(); - - ReferenceImplementations.CopyColorsTo(ref block, new Span(colorsExpected, offset), stride); - - block.CopyColorsTo(new Span(colorsActual, offset), stride, &temp); - - // Output.WriteLine("******* EXPECTED: *********"); - // PrintLinearData(colorsExpected); - // Output.WriteLine("******** ACTUAL: **********"); - Assert.Equal(colorsExpected, colorsActual); - } - private static float[] Create8x8ColorCropTestData() { float[] result = new float[64]; @@ -263,30 +236,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return result; } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void NormalizeColors(bool inplace) + [Fact] + public void NormalizeColors() { var block = default(Block8x8F); float[] input = Create8x8ColorCropTestData(); block.LoadFrom(input); this.Output.WriteLine("Input:"); this.PrintLinearData(input); - - var dest = default(Block8x8F); - if (inplace) - { - dest = block; - dest.NormalizeColorsInplace(); - } - else - { - block.NormalizeColorsInto(ref dest); - } + Block8x8F dest = block; + dest.NormalizeColorsInplace(); - float[] array = new float[64]; dest.CopyTo(array); this.Output.WriteLine("Result:"); From 63e574cdb2fcb14f71a9d271dbf344ba289cc7f3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 17:24:47 +0200 Subject: [PATCH 11/12] more cleanup + more tests --- .../Jpeg/Common/Block8x8F.Generated.cs | 3 +- .../Jpeg/Common/Block8x8F.Generated.tt | 1 + .../Formats/Jpeg/Common/Block8x8F.cs | 2 +- .../Common/Decoder/JpegBlockPostProcessor.cs | 2 +- .../Decoder/JpegColorConverter.FromYCbCr.cs | 68 ++----------------- .../Formats/Jpg/Block8x8FTests.cs | 28 +++++++- 6 files changed, 35 insertions(+), 69 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index e8614205c..246df9d91 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -146,6 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common ref Vector row7 = ref Unsafe.As>(ref this.V7L); row7 = NormalizeAndRound(row7, off, max); - } + + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt index 9ab3ee12c..7b5b6823a 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt @@ -96,6 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common <# } #> + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index ff9db6ab9..fcf618e84 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Destination [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void CopyTo(Span dest) + public void CopyTo(Span dest) { ref byte d = ref Unsafe.As(ref dest.DangerousGetPinnableReference()); ref byte s = ref Unsafe.As(ref this); diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index d0f2b8817..a081c8415 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. /// [StructLayout(LayoutKind.Sequential)] - internal unsafe struct JpegBlockPostProcessor + internal struct JpegBlockPostProcessor { /// /// Source block diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs index fef6d6438..b988695e1 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs @@ -1,6 +1,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Common.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { @@ -124,9 +125,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder if (Vector.Count == 4) { // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?) - r.RoundAndDownscaleBasic(); - g.RoundAndDownscaleBasic(); - b.RoundAndDownscaleBasic(); + r.RoundAndDownscalePreAvx2(); + g.RoundAndDownscalePreAvx2(); + b.RoundAndDownscalePreAvx2(); } else if (SimdUtils.IsAvx2CompatibleArchitecture) { @@ -147,67 +148,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder } } - /// - /// Its faster to process multiple Vector4-s together - /// - private struct Vector4Pair - { - public Vector4 A; - - public Vector4 B; - - private static readonly Vector4 Scale = new Vector4(1 / 255f); - - private static readonly Vector4 Half = new Vector4(0.5f); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RoundAndDownscaleBasic() - { - ref Vector a = ref Unsafe.As>(ref this.A); - a = a.FastRound(); - - ref Vector b = ref Unsafe.As>(ref this.B); - b = b.FastRound(); - - // Downscale by 1/255 - this.A *= Scale; - this.B *= Scale; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RoundAndDownscaleAvx2() - { - ref Vector self = ref Unsafe.As>(ref this); - Vector v = self; - v = v.FastRound(); - - // Downscale by 1/255 - v *= new Vector(1 / 255f); - self = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyInplace(float value) - { - this.A *= value; - this.B *= value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(Vector4 value) - { - this.A += value; - this.B += value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(ref Vector4Pair other) - { - this.A += other.A; - this.B += other.B; - } - } - private struct Vector4Octet { #pragma warning disable SA1132 // Do not combine fields diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 8d1e585db..94f8d2ead 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -32,6 +32,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { } + private bool SkipOnNonAvx2Runner() + { + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + this.Output.WriteLine("AVX2 not supported, skipping!"); + return true; + } + return false; + } + [Fact] public void Indexer() { @@ -263,9 +273,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void NormalizeColorsAndRoundAvx2(int seed) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (this.SkipOnNonAvx2Runner()) { - this.Output.WriteLine("AVX2 not supported, skipping!"); return; } @@ -283,6 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0); } + [Theory] [InlineData(1)] [InlineData(2)] @@ -413,5 +423,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0); } + + [Fact] + public void MultiplyInplace_ByScalar() + { + Block8x8F original = CreateRandomFloatBlock(-500, 500); + + Block8x8F actual = original; + actual.MultiplyInplace(42f); + + for (int i = 0; i < 64; i++) + { + Assert.Equal(original[i]*42f, actual[i]); + } + } } } \ No newline at end of file From 97dc26c01a65df5d7f2e13b65b9538a89087fc0e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 21:45:44 +0200 Subject: [PATCH 12/12] additional Block8x8F cleanup --- src/ImageSharp/Common/Extensions/SimdUtils.cs | 4 +- .../Formats/Jpeg/Common/Block8x8.cs | 82 +---------------- .../Jpeg/Common/Block8x8F.Generated.cs | 88 ++++++++++++++++++- .../Jpeg/Common/Block8x8F.Generated.tt | 40 ++++++++- .../Formats/Jpeg/Common/Block8x8F.cs | 74 ++++++---------- .../Common/Decoder/JpegBlockPostProcessor.cs | 12 +-- .../Jpeg/GolangPort/JpegEncoderCore.cs | 2 +- .../Formats/Jpg/Block8x8FTests.cs | 32 +------ .../Formats/Jpg/JpegProfilingBenchmarks.cs | 4 +- .../Jpg/Utils/ReferenceImplementations.cs | 4 +- 10 files changed, 163 insertions(+), 179 deletions(-) diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.cs b/src/ImageSharp/Common/Extensions/SimdUtils.cs index d6b2fad09..0188bc03c 100644 --- a/src/ImageSharp/Common/Extensions/SimdUtils.cs +++ b/src/ImageSharp/Common/Extensions/SimdUtils.cs @@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp internal static class SimdUtils { /// - /// Indicates AVX2 architecture where both float and integer registers are of size 256 byte. + /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte. /// - public static readonly bool IsAvx2CompatibleArchitecture = Vector.Count == 8 && Vector.Count == 8; + public static bool IsAvx2CompatibleArchitecture => Vector.Count == 8 && Vector.Count == 8; internal static void GuardAvx2(string operation) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs index 1291f160a..96ed0a8c9 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public Block8x8F AsFloatBlock() { var result = default(Block8x8F); - this.CopyToFloatBlock(ref result); + result.LoadFrom(ref this); return result; } @@ -297,85 +297,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - - /// - /// Convert values into as -s - /// - public void CopyToFloatBlock(ref Block8x8F dest) - { - ref short selfRef = ref Unsafe.As(ref this); - - dest.V0L.X = Unsafe.Add(ref selfRef, 0); - dest.V0L.Y = Unsafe.Add(ref selfRef, 1); - dest.V0L.Z = Unsafe.Add(ref selfRef, 2); - dest.V0L.W = Unsafe.Add(ref selfRef, 3); - dest.V0R.X = Unsafe.Add(ref selfRef, 4); - dest.V0R.Y = Unsafe.Add(ref selfRef, 5); - dest.V0R.Z = Unsafe.Add(ref selfRef, 6); - dest.V0R.W = Unsafe.Add(ref selfRef, 7); - - dest.V1L.X = Unsafe.Add(ref selfRef, 8); - dest.V1L.Y = Unsafe.Add(ref selfRef, 9); - dest.V1L.Z = Unsafe.Add(ref selfRef, 10); - dest.V1L.W = Unsafe.Add(ref selfRef, 11); - dest.V1R.X = Unsafe.Add(ref selfRef, 12); - dest.V1R.Y = Unsafe.Add(ref selfRef, 13); - dest.V1R.Z = Unsafe.Add(ref selfRef, 14); - dest.V1R.W = Unsafe.Add(ref selfRef, 15); - - dest.V2L.X = Unsafe.Add(ref selfRef, 16); - dest.V2L.Y = Unsafe.Add(ref selfRef, 17); - dest.V2L.Z = Unsafe.Add(ref selfRef, 18); - dest.V2L.W = Unsafe.Add(ref selfRef, 19); - dest.V2R.X = Unsafe.Add(ref selfRef, 20); - dest.V2R.Y = Unsafe.Add(ref selfRef, 21); - dest.V2R.Z = Unsafe.Add(ref selfRef, 22); - dest.V2R.W = Unsafe.Add(ref selfRef, 23); - - dest.V3L.X = Unsafe.Add(ref selfRef, 24); - dest.V3L.Y = Unsafe.Add(ref selfRef, 25); - dest.V3L.Z = Unsafe.Add(ref selfRef, 26); - dest.V3L.W = Unsafe.Add(ref selfRef, 27); - dest.V3R.X = Unsafe.Add(ref selfRef, 28); - dest.V3R.Y = Unsafe.Add(ref selfRef, 29); - dest.V3R.Z = Unsafe.Add(ref selfRef, 30); - dest.V3R.W = Unsafe.Add(ref selfRef, 31); - - dest.V4L.X = Unsafe.Add(ref selfRef, 32); - dest.V4L.Y = Unsafe.Add(ref selfRef, 33); - dest.V4L.Z = Unsafe.Add(ref selfRef, 34); - dest.V4L.W = Unsafe.Add(ref selfRef, 35); - dest.V4R.X = Unsafe.Add(ref selfRef, 36); - dest.V4R.Y = Unsafe.Add(ref selfRef, 37); - dest.V4R.Z = Unsafe.Add(ref selfRef, 38); - dest.V4R.W = Unsafe.Add(ref selfRef, 39); - - dest.V5L.X = Unsafe.Add(ref selfRef, 40); - dest.V5L.Y = Unsafe.Add(ref selfRef, 41); - dest.V5L.Z = Unsafe.Add(ref selfRef, 42); - dest.V5L.W = Unsafe.Add(ref selfRef, 43); - dest.V5R.X = Unsafe.Add(ref selfRef, 44); - dest.V5R.Y = Unsafe.Add(ref selfRef, 45); - dest.V5R.Z = Unsafe.Add(ref selfRef, 46); - dest.V5R.W = Unsafe.Add(ref selfRef, 47); - - dest.V6L.X = Unsafe.Add(ref selfRef, 48); - dest.V6L.Y = Unsafe.Add(ref selfRef, 49); - dest.V6L.Z = Unsafe.Add(ref selfRef, 50); - dest.V6L.W = Unsafe.Add(ref selfRef, 51); - dest.V6R.X = Unsafe.Add(ref selfRef, 52); - dest.V6R.Y = Unsafe.Add(ref selfRef, 53); - dest.V6R.Z = Unsafe.Add(ref selfRef, 54); - dest.V6R.W = Unsafe.Add(ref selfRef, 55); - - dest.V7L.X = Unsafe.Add(ref selfRef, 56); - dest.V7L.Y = Unsafe.Add(ref selfRef, 57); - dest.V7L.Z = Unsafe.Add(ref selfRef, 58); - dest.V7L.W = Unsafe.Add(ref selfRef, 59); - dest.V7R.X = Unsafe.Add(ref selfRef, 60); - dest.V7R.Y = Unsafe.Add(ref selfRef, 61); - dest.V7R.Z = Unsafe.Add(ref selfRef, 62); - dest.V7R.W = Unsafe.Add(ref selfRef, 63); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index 246df9d91..93e9e0388 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -96,8 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInplace() + public void NormalizeColorsInplace() { this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); @@ -117,11 +116,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4); } + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void NormalizeColorsAndRoundInplaceAvx2() { Vector off = new Vector(128f); Vector max = new Vector(255F); - ref Vector row0 = ref Unsafe.As>(ref this.V0L); row0 = NormalizeAndRound(row0, off, max); @@ -148,5 +150,85 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common row7 = NormalizeAndRound(row7, off, max); } + + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFrom(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); + + this.V0L.X = Unsafe.Add(ref selfRef, 0); + this.V0L.Y = Unsafe.Add(ref selfRef, 1); + this.V0L.Z = Unsafe.Add(ref selfRef, 2); + this.V0L.W = Unsafe.Add(ref selfRef, 3); + this.V0R.X = Unsafe.Add(ref selfRef, 4); + this.V0R.Y = Unsafe.Add(ref selfRef, 5); + this.V0R.Z = Unsafe.Add(ref selfRef, 6); + this.V0R.W = Unsafe.Add(ref selfRef, 7); + + this.V1L.X = Unsafe.Add(ref selfRef, 8); + this.V1L.Y = Unsafe.Add(ref selfRef, 9); + this.V1L.Z = Unsafe.Add(ref selfRef, 10); + this.V1L.W = Unsafe.Add(ref selfRef, 11); + this.V1R.X = Unsafe.Add(ref selfRef, 12); + this.V1R.Y = Unsafe.Add(ref selfRef, 13); + this.V1R.Z = Unsafe.Add(ref selfRef, 14); + this.V1R.W = Unsafe.Add(ref selfRef, 15); + + this.V2L.X = Unsafe.Add(ref selfRef, 16); + this.V2L.Y = Unsafe.Add(ref selfRef, 17); + this.V2L.Z = Unsafe.Add(ref selfRef, 18); + this.V2L.W = Unsafe.Add(ref selfRef, 19); + this.V2R.X = Unsafe.Add(ref selfRef, 20); + this.V2R.Y = Unsafe.Add(ref selfRef, 21); + this.V2R.Z = Unsafe.Add(ref selfRef, 22); + this.V2R.W = Unsafe.Add(ref selfRef, 23); + + this.V3L.X = Unsafe.Add(ref selfRef, 24); + this.V3L.Y = Unsafe.Add(ref selfRef, 25); + this.V3L.Z = Unsafe.Add(ref selfRef, 26); + this.V3L.W = Unsafe.Add(ref selfRef, 27); + this.V3R.X = Unsafe.Add(ref selfRef, 28); + this.V3R.Y = Unsafe.Add(ref selfRef, 29); + this.V3R.Z = Unsafe.Add(ref selfRef, 30); + this.V3R.W = Unsafe.Add(ref selfRef, 31); + + this.V4L.X = Unsafe.Add(ref selfRef, 32); + this.V4L.Y = Unsafe.Add(ref selfRef, 33); + this.V4L.Z = Unsafe.Add(ref selfRef, 34); + this.V4L.W = Unsafe.Add(ref selfRef, 35); + this.V4R.X = Unsafe.Add(ref selfRef, 36); + this.V4R.Y = Unsafe.Add(ref selfRef, 37); + this.V4R.Z = Unsafe.Add(ref selfRef, 38); + this.V4R.W = Unsafe.Add(ref selfRef, 39); + + this.V5L.X = Unsafe.Add(ref selfRef, 40); + this.V5L.Y = Unsafe.Add(ref selfRef, 41); + this.V5L.Z = Unsafe.Add(ref selfRef, 42); + this.V5L.W = Unsafe.Add(ref selfRef, 43); + this.V5R.X = Unsafe.Add(ref selfRef, 44); + this.V5R.Y = Unsafe.Add(ref selfRef, 45); + this.V5R.Z = Unsafe.Add(ref selfRef, 46); + this.V5R.W = Unsafe.Add(ref selfRef, 47); + + this.V6L.X = Unsafe.Add(ref selfRef, 48); + this.V6L.Y = Unsafe.Add(ref selfRef, 49); + this.V6L.Z = Unsafe.Add(ref selfRef, 50); + this.V6L.W = Unsafe.Add(ref selfRef, 51); + this.V6R.X = Unsafe.Add(ref selfRef, 52); + this.V6R.Y = Unsafe.Add(ref selfRef, 53); + this.V6R.Z = Unsafe.Add(ref selfRef, 54); + this.V6R.W = Unsafe.Add(ref selfRef, 55); + + this.V7L.X = Unsafe.Add(ref selfRef, 56); + this.V7L.Y = Unsafe.Add(ref selfRef, 57); + this.V7L.Z = Unsafe.Add(ref selfRef, 58); + this.V7L.W = Unsafe.Add(ref selfRef, 59); + this.V7R.X = Unsafe.Add(ref selfRef, 60); + this.V7R.Y = Unsafe.Add(ref selfRef, 61); + this.V7R.Z = Unsafe.Add(ref selfRef, 62); + this.V7R.W = Unsafe.Add(ref selfRef, 63); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt index 7b5b6823a..dc0996b65 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt @@ -61,8 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInplace() + public void NormalizeColorsInplace() { <# @@ -80,11 +79,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common #> } + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void NormalizeColorsAndRoundInplaceAvx2() { Vector off = new Vector(128f); Vector max = new Vector(255F); - <# for (int i = 0; i < 8; i++) @@ -98,5 +100,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common #> } + + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFrom(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); + + <# + PushIndent(" "); + for (int j = 0; j < 8; j++) + { + for (int i = 0; i < 8; i++) + { + char destCoord = coordz[i % 4]; + char destSide = (i / 4) % 2 == 0 ? 'L' : 'R'; + + if(j > 0 && i == 0){ + WriteLine(""); + } + + char srcCoord = coordz[j % 4]; + char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; + + string expression = $"this.V{j}{destSide}.{destCoord} = Unsafe.Add(ref selfRef, {j*8+i});\r\n"; + Write(expression); + + } + } + PopIndent(); + #> + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index fcf618e84..2dd42288c 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -16,16 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// 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 - - /// - /// Vector count - /// - public const int VectorCount = 16; - /// /// A number of scalar coefficients in a /// @@ -156,36 +146,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - /// - /// Pointer-based "Indexer" (getter part) - /// - /// Block pointer - /// Index - /// The scaleVec value at the specified index - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx) - { - GuardBlockIndex(idx); - - float* fp = (float*)blockPtr; - return fp[idx]; - } - - /// - /// Pointer-based "Indexer" (setter part) - /// - /// Block pointer - /// Index - /// Value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value) - { - GuardBlockIndex(idx); - - float* fp = (float*)blockPtr; - fp[idx] = value; - } - /// /// Fill the block with defaults (zeroes) /// @@ -409,6 +369,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } /// + /// Quantize 'block' into 'dest' using the 'qt' quantization table: /// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values. /// To finish the rounding it's enough to (int)-cast these values. /// @@ -416,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Destination block /// The quantization table /// Pointer to elements of - public static unsafe void UnzigDivRound( + public static unsafe void Quantize( Block8x8F* block, Block8x8F* dest, Block8x8F* qt, @@ -505,15 +466,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) + /// + /// Level shift by +128, clip to [0..255], and round all the values in the block. + /// + public void NormalizeColorsAndRoundInplace() { - row += off; - row = Vector.Max(row, Vector.Zero); - row = Vector.Min(row, max); - return row.FastRound(); + if (SimdUtils.IsAvx2CompatibleArchitecture) + { + this.NormalizeColorsAndRoundInplaceAvx2(); + } + else + { + this.NormalizeColorsInplace(); + this.RoundInplace(); + } } + /// + /// Rounds all values in the block. + /// public void RoundInplace() { for (int i = 0; i < Size; i++) @@ -540,6 +511,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return bld.ToString(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) + { + row += off; + row = Vector.Max(row, Vector.Zero); + row = Vector.Min(row, max); + return row.FastRound(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index a081c8415..574967b6b 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder BufferArea destArea) { ref Block8x8F b = ref this.SourceBlock; - sourceBlock.CopyToFloatBlock(ref b); + b.LoadFrom(ref sourceBlock); // Dequantize: b.MultiplyInplace(ref this.DequantiazationTable); @@ -74,15 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder // To conform better to libjpeg we actually NEED TO loose precision here. // This is because they store blocks as Int16 between all the operations. // To be "more accurate", we need to emulate this by rounding! - if (SimdUtils.IsAvx2CompatibleArchitecture) - { - this.WorkspaceBlock1.NormalizeColorsAndRoundInplaceAvx2(); - } - else - { - this.WorkspaceBlock1.NormalizeColorsInplace(); - this.WorkspaceBlock1.RoundInplace(); - } + this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(); this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 3a24bded3..2deb3f62d 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -564,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2); - Block8x8F.UnzigDivRound(tempDest1, tempDest2, quant, unzigPtr); + Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr); float* unziggedDestPtr = (float*)tempDest2; int dc = (int)unziggedDestPtr[0]; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 94f8d2ead..bf5507676 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -65,31 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }); Assert.Equal(sum, 64f * 63f * 0.5f); } - - [Fact] - public unsafe void Indexer_GetScalarAt_SetScalarAt() - { - float sum = 0; - this.Measure( - Times, - () => - { - var block = new Block8x8F(); - - for (int i = 0; i < Block8x8F.Size; i++) - { - Block8x8F.SetScalarAt(&block, i, i); - } - - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += Block8x8F.GetScalarAt(&block, i); - } - }); - Assert.Equal(sum, 64f * 63f * 0.5f); - } - + [Fact] public void Indexer_ReferenceBenchmarkWithArray() { @@ -296,7 +272,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [InlineData(1)] [InlineData(2)] - public unsafe void UnzigDivRound(int seed) + public unsafe void Quantize(int seed) { var block = new Block8x8F(); block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); @@ -307,11 +283,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var unzig = ZigZag.CreateUnzigTable(); int* expectedResults = stackalloc int[Block8x8F.Size]; - ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data); + ReferenceImplementations.QuantizeRational(&block, expectedResults, &qt, unzig.Data); var actualResults = default(Block8x8F); - Block8x8F.UnzigDivRound(&block, &actualResults, &qt, unzig.Data); + Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data); for (int i = 0; i < Block8x8F.Size; i++) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index a228bc236..baa31f674 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -34,8 +34,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg444, }; - // [Theory] // Benchmark, enable manually - // [MemberData(nameof(DecodeJpegData))] + //[Theory] // Benchmark, enable manually + //[MemberData(nameof(DecodeJpegData))] public void DecodeJpeg_Original(string fileName) { this.DecodeJpegBenchmarkImpl(fileName, new OrigJpegDecoder()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index f3722b5d6..92ead8164 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -108,14 +108,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } /// - /// Reference implementation to test . + /// Reference implementation to test . /// Rounding is done used an integer-based algorithm defined in . /// /// The input block /// The destination block of integers /// The quantization table /// Pointer to - public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) + public static unsafe void QuantizeRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) { float* s = (float*)src; float* q = (float*)qt;