From 8fcb1e68ca4a278fec5ec1802628846dada44c59 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 02:06:19 +0100 Subject: [PATCH 01/15] ImageSharp.Tests46 + better cache in FileProvider --- ImageSharp.sln | 7 + global.json | 2 +- src/ImageSharp/Properties/AssemblyInfo.cs | 1 + .../Formats/Jpg/Block8x8FTests.cs | 5 +- .../ImageProviders/FileProvider.cs | 18 +- tests/ImageSharp.Tests46/HelloTest.cs | 29 +++ .../ImageSharp.Tests46.csproj | 176 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++ tests/ImageSharp.Tests46/TestFile.cs | 73 ++++++++ tests/ImageSharp.Tests46/packages.config | 10 + 10 files changed, 350 insertions(+), 7 deletions(-) create mode 100644 tests/ImageSharp.Tests46/HelloTest.cs create mode 100644 tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj create mode 100644 tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs create mode 100644 tests/ImageSharp.Tests46/TestFile.cs create mode 100644 tests/ImageSharp.Tests46/packages.config diff --git a/ImageSharp.sln b/ImageSharp.sln index f05c47e40..b168c6340 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{815C06 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Tests46", "tests\ImageSharp.Tests46\ImageSharp.Tests46.csproj", "{88C5FB74-5845-4CC0-8F1E-A25EBABC95C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,6 +49,10 @@ Global {299D8E18-102C-42DE-ADBF-79098EE706A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {299D8E18-102C-42DE-ADBF-79098EE706A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {299D8E18-102C-42DE-ADBF-79098EE706A8}.Release|Any CPU.Build.0 = Release|Any CPU + {88C5FB74-5845-4CC0-8F1E-A25EBABC95C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88C5FB74-5845-4CC0-8F1E-A25EBABC95C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88C5FB74-5845-4CC0-8F1E-A25EBABC95C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88C5FB74-5845-4CC0-8F1E-A25EBABC95C2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -55,5 +61,6 @@ Global {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {299D8E18-102C-42DE-ADBF-79098EE706A8} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {88C5FB74-5845-4CC0-8F1E-A25EBABC95C2} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} EndGlobalSection EndGlobal diff --git a/global.json b/global.json index 7346bdc28..6da79b611 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "src" ], "sdk": { - "version": "1.0.0-preview2-003121" + "version": "1.0.0-preview2-003131" } } \ No newline at end of file diff --git a/src/ImageSharp/Properties/AssemblyInfo.cs b/src/ImageSharp/Properties/AssemblyInfo.cs index ec405e1d0..8f3982ee4 100644 --- a/src/ImageSharp/Properties/AssemblyInfo.cs +++ b/src/ImageSharp/Properties/AssemblyInfo.cs @@ -37,3 +37,4 @@ using System.Runtime.CompilerServices; // Ensure the internals can be tested. [assembly: InternalsVisibleTo("ImageSharp.Benchmarks")] [assembly: InternalsVisibleTo("ImageSharp.Tests")] +[assembly: InternalsVisibleTo("ImageSharp.Tests46")] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 524b84082..f2ff23634 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -52,7 +52,7 @@ namespace ImageSharp.Tests.Formats.Jpg public unsafe void Indexer_GetScalarAt_SetScalarAt() { float sum = 0; - Measure(Times, () => + this.Measure(Times, () => { Block8x8F block = new Block8x8F(); @@ -74,8 +74,7 @@ namespace ImageSharp.Tests.Formats.Jpg { float sum = 0; - - Measure(Times, () => + this.Measure(Times, () => { //Block8x8F block = new Block8x8F(); float[] block = new float[64]; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index edd6d03a1..a2848ff6f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -13,8 +13,18 @@ namespace ImageSharp.Tests { private class FileProvider : TestImageProvider { - private static ConcurrentDictionary> cache = - new ConcurrentDictionary>(); + // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider + // are shared between PixelTypes.Color & PixelTypes.StandardImageClass + private class Key : Tuple + { + public Key(PixelTypes item1, string item2) + : base(item1, item2) + { + } + } + + private static ConcurrentDictionary> cache = + new ConcurrentDictionary>(); private string filePath; @@ -27,8 +37,10 @@ namespace ImageSharp.Tests public override Image GetImage() { + var key = new Key(this.PixelType, this.filePath); + var cachedImage = cache.GetOrAdd( - this.filePath, + key, fn => { var testFile = TestFile.Create(this.filePath); diff --git a/tests/ImageSharp.Tests46/HelloTest.cs b/tests/ImageSharp.Tests46/HelloTest.cs new file mode 100644 index 000000000..6a31d9bdd --- /dev/null +++ b/tests/ImageSharp.Tests46/HelloTest.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ImageSharp.Tests +{ + using Xunit; + using Xunit.Abstractions; + + public class HelloTest + { + private ITestOutputHelper output; + + public HelloTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void HelloFoo() + { + TestFile file = TestFile.Create(TestImages.Jpeg.Calliphora); + var img = file.CreateImage(); + this.output.WriteLine(img.Width.ToString()); + } + } +} diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj new file mode 100644 index 000000000..54227f025 --- /dev/null +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -0,0 +1,176 @@ + + + + + Debug + AnyCPU + {88C5FB74-5845-4CC0-8F1E-A25EBABC95C2} + Library + Properties + ImageSharp.Tests + ImageSharp.Tests46 + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + ..\..\src\ImageSharp\bin\Debug\net45\ImageSharp.dll + + + + + ..\..\src\ImageSharp\bin\Release\net45\ImageSharp.dll + + + + + + + + ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll + True + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.2.0-beta4-build3444\lib\netstandard1.0\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.2.0-beta4-build3444\lib\net45\xunit.core.dll + True + + + ..\..\packages\xunit.extensibility.execution.2.2.0-beta4-build3444\lib\net45\xunit.execution.desktop.dll + True + + + + + Formats\Jpg\Block8x8FTests.cs + + + Formats\Jpg\JpegTests.cs + + + Formats\Jpg\ReferenceImplementations.cs + + + Formats\Jpg\UtilityTestClassBase.cs + + + Image\ImagePropertyTests.cs + + + Image\ImageTests.cs + + + Image\PixelAccessorTests.cs + + + TestBase.cs + + + TestImages.cs + + + TestUtilities\ImageDataAttributeBase.cs + + + TestUtilities\WithBlankImageAttribute.cs + + + TestUtilities\WithFileAttribute.cs + + + TestUtilities\WithFileCollectionAttribute.cs + + + TestUtilities\WithMemberFactoryAttribute.cs + + + TestUtilities\WithSolidFilledImagesAttribute.cs + + + TestUtilities\EnumHelper.cs + + + TestUtilities\GenericFactory.cs + + + TestUtilities\ImageFactory.cs + + + TestUtilities\BlankProvider.cs + + + TestUtilities\FileProvider.cs + + + TestUtilities\LambdaProvider.cs + + + TestUtilities\SolidProvider.cs + + + TestUtilities\TestImageProvider.cs + + + TestUtilities\ImagingTestCaseUtility.cs + + + TestUtilities\PixelTypes.cs + + + TestUtilities\TestImageProviderTests.cs + + + TestUtilities\TestUtilityExtensionsTests.cs + + + TestUtilities\TestUtilityExtensions.cs + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs b/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d16593ab0 --- /dev/null +++ b/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageSharp.Tests46")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Sapa")] +[assembly: AssemblyProduct("ImageSharp.Tests46")] +[assembly: AssemblyCopyright("Copyright © Sapa 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("88c5fb74-5845-4cc0-8f1e-a25ebabc95c2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/ImageSharp.Tests46/TestFile.cs b/tests/ImageSharp.Tests46/TestFile.cs new file mode 100644 index 000000000..95e2fc083 --- /dev/null +++ b/tests/ImageSharp.Tests46/TestFile.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Concurrent; + using System.IO; + + public class TestFile + { + private static readonly ConcurrentDictionary cache = new ConcurrentDictionary(); + private static readonly string FormatsDirectory = GetFormatsDirectory(); + + private static string GetFormatsDirectory() + { + return "../../../ImageSharp.Tests/TestImages/Formats/"; + } + + private readonly Image image; + private readonly string file; + + private TestFile(string file) + { + this.file = file; + + this.Bytes = File.ReadAllBytes(file); + this.image = new Image(this.Bytes); + } + + public static TestFile Create(string file) + { + return cache.GetOrAdd(file, (string fileName) => + { + return new TestFile(FormatsDirectory + fileName); + }); + } + + public byte[] Bytes { get; } + + public string FileName + { + get + { + return Path.GetFileName(this.file); + } + } + + public string FileNameWithoutExtension + { + get + { + return Path.GetFileNameWithoutExtension(this.file); + } + } + + public string GetFileName(object value) + { + return this.FileNameWithoutExtension + "-" + value + Path.GetExtension(this.file); + } + + public string GetFileNameWithoutExtension(object value) + { + return this.FileNameWithoutExtension + "-" + value; + } + + public Image CreateImage() + { + return new Image(this.image); + } + } +} diff --git a/tests/ImageSharp.Tests46/packages.config b/tests/ImageSharp.Tests46/packages.config new file mode 100644 index 000000000..4eced5344 --- /dev/null +++ b/tests/ImageSharp.Tests46/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file From 8c2325abe92d371acfdbbda4d30b5748e275b530 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 02:30:17 +0100 Subject: [PATCH 02/15] (re) added DCT, Test & ReferenceImplementations changes --- .../Formats/Jpg/Components/Block8x8F.cs | 61 +- src/ImageSharp/Formats/Jpg/Components/DCT.cs | 395 +++++++++++++ .../Jpg/Components/MutableSpanExtensions.cs | 55 +- .../Formats/Jpg/Block8x8FTests.cs | 337 ++++++----- .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 3 - .../Formats/Jpg/ReferenceImplementations.cs | 539 +++++++++++++++++- .../Jpg/ReferenceImplementationsTests.cs | 144 +++++ .../Formats/Jpg/UtilityTestClassBase.cs | 103 +++- .../Jpg/ReferenceImplementationsTests.cs | 144 +++++ .../ImageSharp.Tests46.csproj | 1 + 10 files changed, 1600 insertions(+), 182 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpg/Components/DCT.cs create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs create mode 100644 tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 8b86e3bd5..35d6044f5 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -160,28 +160,50 @@ namespace ImageSharp.Formats /// /// Multiply in place /// - /// Scalar to multiply by + /// Vector to multiply by [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyAllInplace(Vector4 scalar) + public void MultiplyAllInplace(Vector4 scaleVec) { - this.V0L *= scalar; - this.V0R *= scalar; - this.V1L *= scalar; - this.V1R *= scalar; - this.V2L *= scalar; - this.V2R *= scalar; - this.V3L *= scalar; - this.V3R *= scalar; - this.V4L *= scalar; - this.V4R *= scalar; - this.V5L *= scalar; - this.V5R *= scalar; - this.V6L *= scalar; - this.V6R *= scalar; - this.V7L *= scalar; - this.V7R *= scalar; + 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; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddToAllInplace(Vector4 diff) + { + this.V0L += diff; + this.V0R += diff; + this.V1L += diff; + this.V1R += diff; + this.V2L += diff; + this.V2R += diff; + this.V3L += diff; + this.V3R += diff; + this.V4L += diff; + this.V4R += diff; + this.V5L += diff; + this.V5R += diff; + this.V6L += diff; + this.V6R += diff; + this.V7L += diff; + this.V7R += diff; + } + + /// /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) /// @@ -206,7 +228,7 @@ namespace ImageSharp.Formats /// /// Block pointer /// Index - /// The scalar value at the specified index + /// The scaleVec value at the specified index [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx) { @@ -420,6 +442,7 @@ namespace ImageSharp.Formats this.CopyTo(legacyBlock.Data); } + /// /// Level shift by +128, clip to [0, 255], and write to buffer. /// diff --git a/src/ImageSharp/Formats/Jpg/Components/DCT.cs b/src/ImageSharp/Formats/Jpg/Components/DCT.cs new file mode 100644 index 000000000..a4763a8f5 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/DCT.cs @@ -0,0 +1,395 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// ReSharper disable InconsistentNaming +namespace ImageSharp.Formats +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Contains forward & inverse DCT implementations + /// + internal static class DCT + { + /// + /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) + /// + /// Source + /// Destination + /// Temporary block provided by the caller + public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) + { + src.TransposeInto(ref temp); + IDCT8x4_LeftPart(ref temp, ref dest); + IDCT8x4_RightPart(ref temp, ref dest); + + dest.TransposeInto(ref temp); + + IDCT8x4_LeftPart(ref temp, ref dest); + IDCT8x4_RightPart(ref temp, ref dest); + + dest.MultiplyAllInplace(C_0_125); + } + +#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore + private static readonly Vector4 C_1_175876 = new Vector4(1.175876f); + private static readonly Vector4 C_1_961571 = new Vector4(-1.961571f); + private static readonly Vector4 C_0_390181 = new Vector4(-0.390181f); + private static readonly Vector4 C_0_899976 = new Vector4(-0.899976f); + private static readonly Vector4 C_2_562915 = new Vector4(-2.562915f); + private static readonly Vector4 C_0_298631 = new Vector4(0.298631f); + private static readonly Vector4 C_2_053120 = new Vector4(2.053120f); + private static readonly Vector4 C_3_072711 = new Vector4(3.072711f); + private static readonly Vector4 C_1_501321 = new Vector4(1.501321f); + private static readonly Vector4 C_0_541196 = new Vector4(0.541196f); + private static readonly Vector4 C_1_847759 = new Vector4(-1.847759f); + private static readonly Vector4 C_0_765367 = new Vector4(0.765367f); + + private static readonly Vector4 C_0_125 = new Vector4(0.1250f); +#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore + private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); + + /// + /// Do IDCT internal operations on the left part of the block. Original source: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// + /// Destination block + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 my1 = s.V1L; + Vector4 my7 = s.V7L; + Vector4 mz0 = my1 + my7; + + Vector4 my3 = s.V3L; + Vector4 mz2 = my3 + my7; + Vector4 my5 = s.V5L; + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = ((mz0 + mz1) * C_1_175876); + + mz2 = (mz2 * C_1_961571) + mz4; + mz3 = (mz3 * C_0_390181) + mz4; + mz0 = mz0 * C_0_899976; + mz1 = mz1 * C_2_562915; + + Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; + + Vector4 my2 = s.V2L; + Vector4 my6 = s.V6L; + mz4 = (my2 + my6) * C_0_541196; + Vector4 my0 = s.V0L; + Vector4 my4 = s.V4L; + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + (my6 * C_1_847759); + mz3 = mz4 + (my2 * C_0_765367); + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + + d.V0L = my0 + mb0; + d.V7L = my0 - mb0; + d.V1L = my1 + mb1; + d.V6L = my1 - mb1; + d.V2L = my2 + mb2; + d.V5L = my2 - mb2; + d.V3L = my3 + mb3; + d.V4L = my3 - mb3; + } + + /// + /// Do IDCT internal operations on the right part of the block. + /// Original source: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 my1 = s.V1R; + Vector4 my7 = s.V7R; + Vector4 mz0 = my1 + my7; + + Vector4 my3 = s.V3R; + Vector4 mz2 = my3 + my7; + Vector4 my5 = s.V5R; + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = (mz0 + mz1) * C_1_175876; + + mz2 = (mz2 * C_1_961571) + mz4; + mz3 = (mz3 * C_0_390181) + mz4; + mz0 = mz0 * C_0_899976; + mz1 = mz1 * C_2_562915; + + Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; + + Vector4 my2 = s.V2R; + Vector4 my6 = s.V6R; + mz4 = (my2 + my6) * C_0_541196; + Vector4 my0 = s.V0R; + Vector4 my4 = s.V4R; + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + (my6 * C_1_847759); + mz3 = mz4 + (my2 * C_0_765367); + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + + d.V0R = my0 + mb0; + d.V7R = my0 - mb0; + d.V1R = my1 + mb1; + d.V6R = my1 - mb1; + d.V2R = my2 + mb2; + d.V5R = my2 - mb2; + d.V3R = my3 + mb3; + d.V4R = my3 - mb3; + } + + + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + public static void FDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 c0 = s.V0L; + Vector4 c1 = s.V7L; + Vector4 t0 = (c0 + c1); + Vector4 t7 = (c0 - c1); + + c1 = s.V6L; + c0 = s.V1L; + Vector4 t1 = (c0 + c1); + Vector4 t6 = (c0 - c1); + + c1 = s.V5L; + c0 = s.V2L; + Vector4 t2 = (c0 + c1); + Vector4 t5 = (c0 - c1); + + c0 = s.V3L; + c1 = s.V4L; + Vector4 t3 = (c0 + c1); + Vector4 t4 = (c0 - c1); + + /* + c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; + c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; + c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; + c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; + */ + + c0 = (t0 + t3); + Vector4 c3 = (t0 - t3); + c1 = (t1 + t2); + Vector4 c2 = (t1 - t2); + + /* + c0 = t0 + t3; c3 = t0 - t3; + c1 = t1 + t2; c2 = t1 - t2; + */ + + d.V0L = c0 + c1; + d.V4L = c0 - c1; + + /*y[0] = c0 + c1; + y[4] = c0 - c1;*/ + + Vector4 w0 = new Vector4(0.541196f); + Vector4 w1 = new Vector4(1.306563f); + + + d.V2L = (w0 * c2) + (w1 * c3); + + d.V6L = (w0 * c3) - (w1 * c2); + /* + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + */ + + w0 = new Vector4(1.175876f); + w1 = new Vector4(0.785695f); + c3 = ((w0 * t4) + (w1 * t7)); + c0 = ((w0 * t7) - (w1 * t4)); + /* + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + */ + + w0 = new Vector4(1.387040f); + w1 = new Vector4(0.275899f); + c2 = ((w0 * t5) + (w1 * t6)); + c1 = ((w0 * t6) - (w1 * t5)); + /* + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + */ + + d.V3L = (c0 - c2); + + d.V5L = (c3 - c1); + //y[5] = c3 - c1; y[3] = c0 - c2; + + Vector4 invsqrt2 = new Vector4(0.707107f); + c0 = ((c0 + c2) * invsqrt2); + c3 = ((c3 + c1) * invsqrt2); + //c0 = (c0 + c2) * invsqrt2; + //c3 = (c3 + c1) * invsqrt2; + + d.V1L = (c0 + c3); + + d.V7L = (c0 - c3); + //y[1] = c0 + c3; y[7] = c0 - c3; + + /*for(i = 0;i < 8;i++) + { + y[i] *= invsqrt2h; + }*/ + } + + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + public static void FDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) + { + Vector4 c0 = s.V0R; + Vector4 c1 = s.V7R; + Vector4 t0 = (c0 + c1); + Vector4 t7 = (c0 - c1); + + c1 = s.V6R; + c0 = s.V1R; + Vector4 t1 = (c0 + c1); + Vector4 t6 = (c0 - c1); + + c1 = s.V5R; + c0 = s.V2R; + Vector4 t2 = (c0 + c1); + Vector4 t5 = (c0 - c1); + + c0 = s.V3R; + c1 = s.V4R; + Vector4 t3 = (c0 + c1); + Vector4 t4 = (c0 - c1); + + /* + c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; + c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; + c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; + c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; + */ + + c0 = (t0 + t3); + Vector4 c3 = (t0 - t3); + c1 = (t1 + t2); + Vector4 c2 = (t1 - t2); + + /* + c0 = t0 + t3; c3 = t0 - t3; + c1 = t1 + t2; c2 = t1 - t2; + */ + + d.V0R = c0 + c1; + d.V4R = c0 - c1; + + /*y[0] = c0 + c1; + y[4] = c0 - c1;*/ + + Vector4 w0 = new Vector4(0.541196f); + Vector4 w1 = new Vector4(1.306563f); + + + d.V2R = (w0 * c2) + (w1 * c3); + + d.V6R = (w0 * c3) - (w1 * c2); + /* + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + */ + + w0 = new Vector4(1.175876f); + w1 = new Vector4(0.785695f); + c3 = ((w0 * t4) + (w1 * t7)); + c0 = ((w0 * t7) - (w1 * t4)); + /* + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + */ + + w0 = new Vector4(1.387040f); + w1 = new Vector4(0.275899f); + c2 = ((w0 * t5) + (w1 * t6)); + c1 = ((w0 * t6) - (w1 * t5)); + /* + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + */ + + d.V3R = (c0 - c2); + + d.V5R = (c3 - c1); + //y[5] = c3 - c1; y[3] = c0 - c2; + + c0 = ((c0 + c2) * InvSqrt2); + c3 = ((c3 + c1) * InvSqrt2); + //c0 = (c0 + c2) * invsqrt2; + //c3 = (c3 + c1) * invsqrt2; + + d.V1R = (c0 + c3); + + d.V7R = (c0 - c3); + //y[1] = c0 + c3; y[7] = c0 - c3; + + /*for(i = 0;i < 8;i++) + { + y[i] *= invsqrt2h; + }*/ + } + + public static void TransformFDCT(ref Block8x8F s, ref Block8x8F d, ref Block8x8F temp, bool offsetSourceByNeg128 = true) + { + s.TransposeInto(ref temp); + if (offsetSourceByNeg128) + { + temp.AddToAllInplace(new Vector4(-128)); + } + + FDCT8x4_LeftPart(ref temp, ref d); + FDCT8x4_RightPart(ref temp, ref d); + + d.TransposeInto(ref temp); + + FDCT8x4_LeftPart(ref temp, ref d); + FDCT8x4_RightPart(ref temp, ref d); + + d.MultiplyAllInplace(C_0_125); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs b/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs index 42edcd3c4..7253a816e 100644 --- a/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs +++ b/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats.Jpg.Components +namespace ImageSharp.Formats { using System.Numerics; using System.Runtime.CompilerServices; @@ -77,5 +77,58 @@ namespace ImageSharp.Formats.Jpg.Components data[2] = (int)v.Z; data[3] = (int)v.W; } + + + + public static MutableSpan ConvertToFloat32MutableSpan(this MutableSpan src) + { + MutableSpan result = new MutableSpan(src.TotalCount); + for (int i = 0; i < src.TotalCount; i++) + { + result[i] = (float)src[i]; + } + return result; + } + + public static MutableSpan ConvertToInt32MutableSpan(this MutableSpan src) + { + MutableSpan result = new MutableSpan(src.TotalCount); + for (int i = 0; i < src.TotalCount; i++) + { + result[i] = (int)src[i]; + } + return result; + } + + public static MutableSpan AddScalarToAllValues(this MutableSpan src, float scalar) + { + MutableSpan result = new MutableSpan(src.TotalCount); + for (int i = 0; i < src.TotalCount; i++) + { + result[i] = src[i] + scalar; + } + return result; + } + + public static MutableSpan AddScalarToAllValues(this MutableSpan src, int scalar) + { + MutableSpan result = new MutableSpan(src.TotalCount); + for (int i = 0; i < src.TotalCount; i++) + { + result[i] = src[i] + scalar; + } + return result; + } + + + public static MutableSpan Copy(this MutableSpan src) + { + MutableSpan result = new MutableSpan(src.TotalCount); + for (int i = 0; i < src.TotalCount; i++) + { + result[i] = src[i]; + } + return result; + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index f2ff23634..8939d2c20 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -1,20 +1,18 @@ // Uncomment this to turn unit tests into benchmarks: //#define BENCHMARKING -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using ImageSharp.Formats; -using Xunit; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace ImageSharp.Tests.Formats.Jpg { + using System.Diagnostics; + using System.Numerics; + + using ImageSharp.Formats; + + using Xunit; + using Xunit.Abstractions; + public class Block8x8FTests : UtilityTestClassBase { #if BENCHMARKING @@ -23,7 +21,8 @@ namespace ImageSharp.Tests.Formats.Jpg public const int Times = 1; #endif - public Block8x8FTests(ITestOutputHelper output) : base(output) + public Block8x8FTests(ITestOutputHelper output) + : base(output) { } @@ -31,20 +30,23 @@ namespace ImageSharp.Tests.Formats.Jpg public void Indexer() { float sum = 0; - this.Measure(Times, () => - { - Block8x8F block = new Block8x8F(); - - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - block[i] = i; - } - sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - sum += block[i]; - } - }); + this.Measure( + Times, + () => + { + Block8x8F block = new Block8x8F(); + + for (int i = 0; i < Block8x8F.ScalarCount; i++) + { + block[i] = i; + } + + sum = 0; + for (int i = 0; i < Block8x8F.ScalarCount; i++) + { + sum += block[i]; + } + }); Assert.Equal(sum, 64f * 63f * 0.5f); } @@ -52,21 +54,24 @@ namespace ImageSharp.Tests.Formats.Jpg public unsafe void Indexer_GetScalarAt_SetScalarAt() { float sum = 0; - this.Measure(Times, () => - { - Block8x8F block = new Block8x8F(); - - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - Block8x8F.SetScalarAt(&block, i, i); - } - sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - sum += Block8x8F.GetScalarAt(&block, i); - } - }); - Assert.Equal(sum, 64f*63f*0.5f); + this.Measure( + Times, + () => + { + Block8x8F block = new Block8x8F(); + + for (int i = 0; i < Block8x8F.ScalarCount; i++) + { + Block8x8F.SetScalarAt(&block, i, i); + } + + sum = 0; + for (int i = 0; i < Block8x8F.ScalarCount; i++) + { + sum += Block8x8F.GetScalarAt(&block, i); + } + }); + Assert.Equal(sum, 64f * 63f * 0.5f); } [Fact] @@ -74,21 +79,24 @@ namespace ImageSharp.Tests.Formats.Jpg { float sum = 0; - this.Measure(Times, () => - { - //Block8x8F block = new Block8x8F(); - float[] block = new float[64]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - block[i] = i; - } - sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - sum += block[i]; - } - }); - Assert.Equal(sum, 64f*63f*0.5f); + this.Measure( + Times, + () => + { + // Block8x8F block = new Block8x8F(); + float[] block = new float[64]; + for (int i = 0; i < Block8x8F.ScalarCount; i++) + { + block[i] = i; + } + + sum = 0; + for (int i = 0; i < Block8x8F.ScalarCount; i++) + { + sum += block[i]; + } + }); + Assert.Equal(sum, 64f * 63f * 0.5f); } [Fact] @@ -101,15 +109,19 @@ namespace ImageSharp.Tests.Formats.Jpg { data[i] = i; } - Measure(Times, () => - { - Block8x8F b = new Block8x8F(); - b.LoadFrom(data); - b.CopyTo(mirror); - }); + + this.Measure( + Times, + () => + { + Block8x8F b = new Block8x8F(); + b.LoadFrom(data); + b.CopyTo(mirror); + }); Assert.Equal(data, mirror); - //PrintLinearData((MutableSpan)mirror); + + // PrintLinearData((MutableSpan)mirror); } [Fact] @@ -122,15 +134,19 @@ namespace ImageSharp.Tests.Formats.Jpg { data[i] = i; } - Measure(Times, () => - { - Block8x8F b = new Block8x8F(); - Block8x8F.LoadFrom(&b, data); - Block8x8F.CopyTo(&b, mirror); - }); + + this.Measure( + Times, + () => + { + Block8x8F b = new Block8x8F(); + Block8x8F.LoadFrom(&b, data); + Block8x8F.CopyTo(&b, mirror); + }); Assert.Equal(data, mirror); - //PrintLinearData((MutableSpan)mirror); + + // PrintLinearData((MutableSpan)mirror); } [Fact] @@ -143,19 +159,21 @@ namespace ImageSharp.Tests.Formats.Jpg { data[i] = i; } - Measure(Times, () => - { - Block8x8F v = new Block8x8F(); - v.LoadFrom(data); - v.CopyTo(mirror); - }); - + this.Measure( + Times, + () => + { + Block8x8F v = new Block8x8F(); + v.LoadFrom(data); + v.CopyTo(mirror); + }); + Assert.Equal(data, mirror); - //PrintLinearData((MutableSpan)mirror); + + // PrintLinearData((MutableSpan)mirror); } - - + [Fact] public void TransposeInto() { @@ -173,8 +191,6 @@ namespace ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual); } - - private class BufferHolder { @@ -188,7 +204,7 @@ namespace ImageSharp.Tests.Formats.Jpg source.Buffer.LoadFrom(Create8x8FloatData()); BufferHolder dest = new BufferHolder(); - Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ..."); + this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ..."); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Times; i++) @@ -197,10 +213,9 @@ namespace ImageSharp.Tests.Formats.Jpg } sw.Stop(); - Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); - + this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); } - + [Fact] public void iDCT2D8x4_LeftPart() { @@ -214,14 +229,14 @@ namespace ImageSharp.Tests.Formats.Jpg Block8x8F dest = new Block8x8F(); - source.IDCT8x4_LeftPart(ref dest); + DCT.IDCT8x4_LeftPart(ref source, ref dest); float[] actualDestArray = new float[64]; dest.CopyTo(actualDestArray); - Print8x8Data(expectedDestArray); - Output.WriteLine("**************"); - Print8x8Data(actualDestArray); + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); Assert.Equal(expectedDestArray, actualDestArray); } @@ -239,65 +254,49 @@ namespace ImageSharp.Tests.Formats.Jpg Block8x8F dest = new Block8x8F(); - source.IDCT8x4_RightPart(ref dest); + DCT.IDCT8x4_RightPart(ref source, ref dest); float[] actualDestArray = new float[64]; dest.CopyTo(actualDestArray); - Print8x8Data(expectedDestArray); - Output.WriteLine("**************"); - Print8x8Data(actualDestArray); + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); Assert.Equal(expectedDestArray.Data, actualDestArray); } - private struct ApproximateFloatComparer : IEqualityComparer - { - private const float Eps = 0.0001f; - - public bool Equals(float x, float y) - { - float d = x - y; - - return d > -Eps && d < Eps; - } - - public int GetHashCode(float obj) - { - throw new InvalidOperationException(); - } - } - - [Fact] - public void IDCTInto() + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void TransformIDCT(int seed) { - float[] sourceArray = Create8x8FloatData(); + var sourceArray = Create8x8RandomFloatData(-200, 200, seed); float[] expectedDestArray = new float[64]; float[] tempArray = new float[64]; ReferenceImplementations.iDCT2D_llm(sourceArray, expectedDestArray, tempArray); - //ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray); - + // ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray); Block8x8F source = new Block8x8F(); source.LoadFrom(sourceArray); Block8x8F dest = new Block8x8F(); Block8x8F tempBuffer = new Block8x8F(); - source.TransformIDCTInto(ref dest, ref tempBuffer); + DCT.TransformIDCT(ref source, ref dest, ref tempBuffer); float[] actualDestArray = new float[64]; dest.CopyTo(actualDestArray); - Print8x8Data(expectedDestArray); - Output.WriteLine("**************"); - Print8x8Data(actualDestArray); - Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer()); - Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer()); + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); + Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); + Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); } - [Fact] public unsafe void CopyColorsTo() { @@ -308,24 +307,23 @@ namespace ImageSharp.Tests.Formats.Jpg int stride = 256; int height = 42; - int offset = height*10 + 20; + int offset = height * 10 + 20; - byte[] colorsExpected = new byte[stride*height]; - byte[] colorsActual = new byte[stride*height]; + byte[] colorsExpected = new byte[stride * height]; + byte[] colorsActual = new byte[stride * height]; Block8x8F temp = new Block8x8F(); - + ReferenceImplementations.CopyColorsTo(ref block, new MutableSpan(colorsExpected, offset), stride); block.CopyColorsTo(new MutableSpan(colorsActual, offset), stride, &temp); - //Output.WriteLine("******* EXPECTED: *********"); - //PrintLinearData(colorsExpected); - //Output.WriteLine("******** ACTUAL: **********"); - + // Output.WriteLine("******* EXPECTED: *********"); + // PrintLinearData(colorsExpected); + // Output.WriteLine("******** ACTUAL: **********"); Assert.Equal(colorsExpected, colorsActual); } - + private static float[] Create8x8ColorCropTestData() { float[] result = new float[64]; @@ -336,6 +334,7 @@ namespace ImageSharp.Tests.Formats.Jpg result[i * 8 + j] = -300 + i * 100 + j * 10; } } + return result; } @@ -345,22 +344,88 @@ namespace ImageSharp.Tests.Formats.Jpg Block8x8F block = new Block8x8F(); var input = Create8x8ColorCropTestData(); block.LoadFrom(input); - Output.WriteLine("Input:"); - PrintLinearData(input); - + this.Output.WriteLine("Input:"); + this.PrintLinearData(input); Block8x8F dest = new Block8x8F(); block.TransformByteConvetibleColorValuesInto(ref dest); float[] array = new float[64]; dest.CopyTo(array); - Output.WriteLine("Result:"); - PrintLinearData(array); + this.Output.WriteLine("Result:"); + this.PrintLinearData(array); foreach (float val in array) { Assert.InRange(val, 0, 255); } } + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_LeftPart(int seed) + { + var src = Create8x8RandomFloatData(-200, 200, seed); + var srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + var destBlock = new Block8x8F(); + + var expectedDest = new MutableSpan(64); + + ReferenceImplementations.fDCT2D8x4_32f(src, expectedDest); + DCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); + + var actualDest = new MutableSpan(64); + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_RightPart(int seed) + { + var src = Create8x8RandomFloatData(-200, 200, seed); + var srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + var destBlock = new Block8x8F(); + + var expectedDest = new MutableSpan(64); + + ReferenceImplementations.fDCT2D8x4_32f(src.Slice(4), expectedDest.Slice(4)); + DCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); + + var actualDest = new MutableSpan(64); + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformFDCT(int seed) + { + var src = Create8x8RandomFloatData(-200, 200, seed); + var srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + var destBlock = new Block8x8F(); + + var expectedDest = new MutableSpan(64); + var temp1 = new MutableSpan(64); + var temp2 = new Block8x8F(); + + ReferenceImplementations.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + DCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); + + var actualDest = new MutableSpan(64); + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 11d535fb8..6a9267fec 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -10,9 +10,6 @@ namespace ImageSharp.Tests { public class JpegTests { - - public const string TestOutputDirectory = "TestOutput/Jpeg"; - private ITestOutputHelper Output { get; } public JpegTests(ITestOutputHelper output) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index cee236adf..006e95963 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs @@ -2,6 +2,7 @@ namespace ImageSharp.Tests.Formats.Jpg { + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,7 +10,7 @@ namespace ImageSharp.Tests.Formats.Jpg /// /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests - /// DCT code Ported from https://github.com/norishigefukushima/dct_simd + /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd /// internal static class ReferenceImplementations { @@ -46,6 +47,310 @@ namespace ImageSharp.Tests.Formats.Jpg } } + public static class IntegerReferenceDCT + { + private const int fix_0_298631336 = 2446; + private const int fix_0_390180644 = 3196; + private const int fix_0_541196100 = 4433; + private const int fix_0_765366865 = 6270; + private const int fix_0_899976223 = 7373; + private const int fix_1_175875602 = 9633; + private const int fix_1_501321110 = 12299; + private const int fix_1_847759065 = 15137; + private const int fix_1_961570560 = 16069; + private const int fix_2_053119869 = 16819; + private const int fix_2_562915447 = 20995; + private const int fix_3_072711026 = 25172; + + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + /// + /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. + /// Leave results scaled up by an overall factor of 8. + /// + /// The block of coefficients. + public static void TransformFDCTInplace(MutableSpan block) + { + // Pass 1: process rows. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; + + int tmp0 = x0 + x7; + int tmp1 = x1 + x6; + int tmp2 = x2 + x5; + int tmp3 = x3 + x4; + + int tmp10 = tmp0 + tmp3; + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = x0 - x7; + tmp1 = x1 - x6; + tmp2 = x2 - x5; + tmp3 = x3 - x4; + + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits - Pass1Bits - 1); + block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits - Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); + } + + // Pass 2: process columns. + // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. + for (int x = 0; x < 8; x++) + { + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; + + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; + + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; + + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits + Pass1Bits - 1); + block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits + Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); + block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); + } + + } + private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int w1pw7 = w1 + w7; + private const int w1mw7 = w1 - w7; + private const int w2pw6 = w2 + w6; + private const int w2mw6 = w2 - w6; + private const int w3pw5 = w3 + w5; + private const int w3mw5 = w3 - w5; + + private const int r2 = 181; // 256/sqrt(2) + + /// + /// Performs a 2-D Inverse Discrete Cosine Transformation. + /// + /// The input coefficients should already have been multiplied by the + /// appropriate quantization table. We use fixed-point computation, with the + /// number of bits for the fractional component varying over the intermediate + /// stages. + /// + /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the + /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on + /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. + /// + /// The source block of coefficients + public static void TransformIDCTInplace(MutableSpan src) + { + // Horizontal 1-D IDCT. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + // If all the AC components are zero, then the IDCT is trivial. + if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && + src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) + { + int dc = src[y8 + 0] << 3; + src[y8 + 0] = dc; + src[y8 + 1] = dc; + src[y8 + 2] = dc; + src[y8 + 3] = dc; + src[y8 + 4] = dc; + src[y8 + 5] = dc; + src[y8 + 6] = dc; + src[y8 + 7] = dc; + continue; + } + + // Prescale. + int x0 = (src[y8 + 0] << 11) + 128; + int x1 = src[y8 + 4] << 11; + int x2 = src[y8 + 6]; + int x3 = src[y8 + 2]; + int x4 = src[y8 + 1]; + int x5 = src[y8 + 7]; + int x6 = src[y8 + 5]; + int x7 = src[y8 + 3]; + + // Stage 1. + int x8 = w7 * (x4 + x5); + x4 = x8 + (w1mw7 * x4); + x5 = x8 - (w1pw7 * x5); + x8 = w3 * (x6 + x7); + x6 = x8 - (w3mw5 * x6); + x7 = x8 - (w3pw5 * x7); + + // Stage 2. + x8 = x0 + x1; + x0 -= x1; + x1 = w6 * (x3 + x2); + x2 = x1 - (w2pw6 * x2); + x3 = x1 + (w2mw6 * x3); + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + // Stage 3. + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = ((r2 * (x4 + x5)) + 128) >> 8; + x4 = ((r2 * (x4 - x5)) + 128) >> 8; + + // Stage 4. + src[y8 + 0] = (x7 + x1) >> 8; + src[y8 + 1] = (x3 + x2) >> 8; + src[y8 + 2] = (x0 + x4) >> 8; + src[y8 + 3] = (x8 + x6) >> 8; + src[y8 + 4] = (x8 - x6) >> 8; + src[y8 + 5] = (x0 - x4) >> 8; + src[y8 + 6] = (x3 - x2) >> 8; + src[y8 + 7] = (x7 - x1) >> 8; + } + + // Vertical 1-D IDCT. + for (int x = 0; x < 8; x++) + { + // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. + // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so + // we do not bother to check for the all-zero case. + + // Prescale. + int y0 = (src[x] << 8) + 8192; + int y1 = src[32 + x] << 8; + int y2 = src[48 + x]; + int y3 = src[16 + x]; + int y4 = src[8 + x]; + int y5 = src[56 + x]; + int y6 = src[40 + x]; + int y7 = src[24 + x]; + + // Stage 1. + int y8 = (w7 * (y4 + y5)) + 4; + y4 = (y8 + (w1mw7 * y4)) >> 3; + y5 = (y8 - (w1pw7 * y5)) >> 3; + y8 = (w3 * (y6 + y7)) + 4; + y6 = (y8 - (w3mw5 * y6)) >> 3; + y7 = (y8 - (w3pw5 * y7)) >> 3; + + // Stage 2. + y8 = y0 + y1; + y0 -= y1; + y1 = (w6 * (y3 + y2)) + 4; + y2 = (y1 - (w2pw6 * y2)) >> 3; + y3 = (y1 + (w2mw6 * y3)) >> 3; + y1 = y4 + y6; + y4 -= y6; + y6 = y5 + y7; + y5 -= y7; + + // Stage 3. + y7 = y8 + y3; + y8 -= y3; + y3 = y0 + y2; + y0 -= y2; + y2 = ((r2 * (y4 + y5)) + 128) >> 8; + y4 = ((r2 * (y4 - y5)) + 128) >> 8; + + // Stage 4. + src[x] = (y7 + y1) >> 14; + src[8 + x] = (y3 + y2) >> 14; + src[16 + x] = (y0 + y4) >> 14; + src[24 + x] = (y8 + y6) >> 14; + src[32 + x] = (y8 - y6) >> 14; + src[40 + x] = (y0 - y4) >> 14; + src[48 + x] = (y3 - y2) >> 14; + src[56 + x] = (y7 - y1) >> 14; + } + } + } + /// /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 /// @@ -56,9 +361,11 @@ namespace ImageSharp.Tests.Formats.Jpg float a0, a1, a2, a3, b0, b1, b2, b3; float z0, z1, z2, z3, z4; + float r0 = 1.414214f; float r1 = 1.387040f; float r2 = 1.306563f; float r3 = 1.175876f; + float r4 = 1.000000f; float r5 = 0.785695f; float r6 = 0.541196f; float r7 = 0.275899f; @@ -130,6 +437,145 @@ namespace ImageSharp.Tests.Formats.Jpg } } + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + public static void fDCT2D8x4_32f(MutableSpan s, MutableSpan d) + { + Vector4 c0 = _mm_load_ps(s, 0); + Vector4 c1 = _mm_load_ps(s, 56); + Vector4 t0 = (c0 + c1); + Vector4 t7 = (c0 - c1); + + c1 = _mm_load_ps(s, 48); + c0 = _mm_load_ps(s, 8); + Vector4 t1 = (c0 + c1); + Vector4 t6 = (c0 - c1); + + c1 = _mm_load_ps(s, 40); + c0 = _mm_load_ps(s, 16); + Vector4 t2 = (c0 + c1); + Vector4 t5 = (c0 - c1); + + c0 = _mm_load_ps(s, 24); + c1 = _mm_load_ps(s, 32); + Vector4 t3 = (c0 + c1); + Vector4 t4 = (c0 - c1); + + /* + c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; + c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; + c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; + c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; + */ + + c0 = (t0 + t3); + Vector4 c3 = (t0 - t3); + c1 = (t1 + t2); + Vector4 c2 = (t1 - t2); + + /* + c0 = t0 + t3; c3 = t0 - t3; + c1 = t1 + t2; c2 = t1 - t2; + */ + + _mm_store_ps(d, 0, (c0 + c1)); + + _mm_store_ps(d, 32, (c0 - c1)); + + /*y[0] = c0 + c1; + y[4] = c0 - c1;*/ + + Vector4 w0 = new Vector4(0.541196f); + Vector4 w1 = new Vector4(1.306563f); + + _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); + + _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); + /* + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + */ + + w0 = new Vector4(1.175876f); + w1 = new Vector4(0.785695f); + c3 = ((w0 * t4) + (w1 * t7)); + c0 = ((w0 * t7) - (w1 * t4)); + /* + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + */ + + w0 = new Vector4(1.387040f); + w1 = new Vector4(0.275899f); + c2 = ((w0 * t5) + (w1 * t6)); + c1 = ((w0 * t6) - (w1 * t5)); + /* + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + */ + + _mm_store_ps(d, 24, (c0 - c2)); + + _mm_store_ps(d, 40, (c3 - c1)); + //y[5] = c3 - c1; y[3] = c0 - c2; + + Vector4 invsqrt2 = new Vector4(0.707107f); + c0 = ((c0 + c2) * invsqrt2); + c3 = ((c3 + c1) * invsqrt2); + //c0 = (c0 + c2) * invsqrt2; + //c3 = (c3 + c1) * invsqrt2; + + _mm_store_ps(d, 8, (c0 + c3)); + + _mm_store_ps(d, 56, (c0 - c3)); + //y[1] = c0 + c3; y[7] = c0 - c3; + + /*for(i = 0;i < 8;i++) + { + y[i] *= invsqrt2h; + }*/ + } + + public static void fDCT8x8_llm_sse(MutableSpan s, MutableSpan d, MutableSpan temp) + { + Transpose8x8(s, temp); + + fDCT2D8x4_32f(temp, d); + + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + + Transpose8x8(d, temp); + + fDCT2D8x4_32f(temp, d); + + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + + Vector4 c = new Vector4(0.1250f); + + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//0 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//1 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//2 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//3 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//4 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//5 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//6 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//7 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//8 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//9 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//10 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//11 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//12 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//13 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//14 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//15 + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 _mm_load_ps(MutableSpan src, int offset) { @@ -329,5 +775,96 @@ namespace ImageSharp.Tests.Formats.Jpg } } } + + internal static void fDCT1Dllm_32f(MutableSpan x, MutableSpan y) + { + float t0, t1, t2, t3, t4, t5, t6, t7; + float c0, c1, c2, c3; + float[] r = new float[8]; + + //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + r[0] = 1.414214f; + r[1] = 1.387040f; + r[2] = 1.306563f; + r[3] = 1.175876f; + r[4] = 1.000000f; + r[5] = 0.785695f; + r[6] = 0.541196f; + r[7] = 0.275899f; + + const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); + const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; + + c1 = x[0]; + c2 = x[7]; + t0 = c1 + c2; + t7 = c1 - c2; + c1 = x[1]; + c2 = x[6]; + t1 = c1 + c2; + t6 = c1 - c2; + c1 = x[2]; + c2 = x[5]; + t2 = c1 + c2; + t5 = c1 - c2; + c1 = x[3]; + c2 = x[4]; + t3 = c1 + c2; + t4 = c1 - c2; + + c0 = t0 + t3; + c3 = t0 - t3; + c1 = t1 + t2; + c2 = t1 - t2; + + y[0] = c0 + c1; + y[4] = c0 - c1; + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + + y[5] = c3 - c1; + y[3] = c0 - c2; + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; + y[1] = c0 + c3; + y[7] = c0 - c3; + } + + internal static void fDCT2D_llm( + MutableSpan s, + MutableSpan d, + MutableSpan temp, + bool downscaleBy8 = false, + bool offsetSourceByNeg128 = false) + { + MutableSpan sWorker = offsetSourceByNeg128 ? s.AddScalarToAllValues(-128f) : s; + + for (int j = 0; j < 8; j++) + { + fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + } + + Transpose8x8(temp, d); + + for (int j = 0; j < 8; j++) + { + fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } + + Transpose8x8(temp, d); + + if (downscaleBy8) + { + for (int j = 0; j < 64; j++) + { + d[j] *= 0.125f; + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs new file mode 100644 index 000000000..265d6b5f2 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -0,0 +1,144 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Formats.Jpg +{ + using System.Numerics; + using ImageSharp.Formats; + using Xunit; + using Xunit.Abstractions; + + public class ReferenceImplementationsTests : UtilityTestClassBase + { + public ReferenceImplementationsTests(ITestOutputHelper output) + : base(output) + { + } + + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) + { + MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); + MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); + + ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData); + + MutableSpan dest = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp); + + for (int i = 0; i < 64; i++) + { + float expected = intData[i]; + float actual = dest[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); + } + } + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void IntegerDCT_ForwardThenInverse(int seed, int startAt) + { + MutableSpan original = Create8x8RandomIntData(-200, 200, seed); + + var block = original.AddScalarToAllValues(128); + + ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block); + + for (int i = 0; i < 64; i++) + { + block[i] /= 8; + } + + ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block); + + for (int i = startAt; i < 64; i++) + { + float expected = original[i]; + float actual = (float)block[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); + } + + } + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt) + { + var data = Create8x8RandomIntData(-200, 200, seed); + MutableSpan src = new MutableSpan(data).ConvertToFloat32MutableSpan(); + MutableSpan dest = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.fDCT2D_llm(src, dest, temp, true); + ReferenceImplementations.iDCT2D_llm(dest, src, temp); + + for (int i = startAt; i < 64; i++) + { + float expected = data[i]; + float actual = (float)src[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); + } + } + + [Fact] + public void HowMuchIsTheFish() + { + Output.WriteLine(Vector.Count.ToString()); + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) + { + MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); + MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); + + ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData); + + MutableSpan dest = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); + + for (int i = 0; i < 64; i++) + { + float expected = intData[i]; + float actual = dest[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void Fdct_SimdReferenceImplementation_IsEquivalentToFloatingPointReferenceImplementation(int seed) + { + Block classic = new Block() { Data = Create8x8RandomIntData(-200, 200, seed) }; + MutableSpan src = new MutableSpan(classic.Data).ConvertToFloat32MutableSpan(); + + MutableSpan dest1 = new MutableSpan(64); + MutableSpan dest2 = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.fDCT2D_llm(src, dest1, temp, downscaleBy8: true, offsetSourceByNeg128: false); + ReferenceImplementations.fDCT8x8_llm_sse(src, dest2, temp); + + Assert.Equal(dest1.Data, dest2.Data, new ApproximateFloatComparer(1f)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs index 55e609a52..20a9d34b1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs @@ -1,21 +1,51 @@ -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text; +using System.Text; using ImageSharp.Formats; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace ImageSharp.Tests.Formats.Jpg { - public class UtilityTestClassBase + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.CompilerServices; + + /// + /// Utility class to measure the execution of an operation. + /// + public class MeasureFixture { - public UtilityTestClassBase(ITestOutputHelper output) + protected bool EnablePrinting = true; + + protected void Measure(int times, Action action, [CallerMemberName] string operationName = null) + { + if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} X {times} ..."); + Stopwatch sw = Stopwatch.StartNew(); + + for (int i = 0; i < times; i++) + { + action(); + } + + sw.Stop(); + if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); + } + + public MeasureFixture(ITestOutputHelper output) { - Output = output; + this.Output = output; } protected ITestOutputHelper Output { get; } + } + + public class UtilityTestClassBase : MeasureFixture + { + public UtilityTestClassBase(ITestOutputHelper output) : base(output) + { + } + // ReSharper disable once InconsistentNaming public static float[] Create8x8FloatData() { @@ -29,10 +59,7 @@ namespace ImageSharp.Tests.Formats.Jpg } return result; } - - - - + // ReSharper disable once InconsistentNaming public static int[] Create8x8IntData() { @@ -47,7 +74,25 @@ namespace ImageSharp.Tests.Formats.Jpg return result; } - internal void Print8x8Data(MutableSpan data) => Print8x8Data(data.Data); + // ReSharper disable once InconsistentNaming + public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) + { + Random rnd = new Random(seed); + int[] result = new int[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = rnd.Next(minValue, maxValue); + } + } + return result; + } + + internal static MutableSpan Create8x8RandomFloatData(int minValue, int maxValue, int seed = 42) + => new MutableSpan(Create8x8RandomIntData(minValue, maxValue, seed)).ConvertToFloat32MutableSpan(); + + internal void Print8x8Data(MutableSpan data) => this.Print8x8Data(data.Data); internal void Print8x8Data(T[] data) { @@ -61,10 +106,10 @@ namespace ImageSharp.Tests.Formats.Jpg bld.AppendLine(); } - Output.WriteLine(bld.ToString()); + this.Output.WriteLine(bld.ToString()); } - internal void PrintLinearData(T[] data) => PrintLinearData(new MutableSpan(data), data.Length); + internal void PrintLinearData(T[] data) => this.PrintLinearData(new MutableSpan(data), data.Length); internal void PrintLinearData(MutableSpan data, int count = -1) { @@ -75,21 +120,35 @@ namespace ImageSharp.Tests.Formats.Jpg { bld.Append($"{data[i],3} "); } - Output.WriteLine(bld.ToString()); + this.Output.WriteLine(bld.ToString()); } - protected void Measure(int times, Action action, [CallerMemberName] string operationName = null) + internal struct ApproximateFloatComparer : IEqualityComparer { - Output.WriteLine($"{operationName} X {times} ..."); - Stopwatch sw = Stopwatch.StartNew(); + private readonly float Eps; - for (int i = 0; i < times; i++) + public ApproximateFloatComparer(float eps = 1f) { - action(); + this.Eps = eps; } - sw.Stop(); - Output.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); + public bool Equals(float x, float y) + { + float d = x - y; + + return d > -this.Eps && d < this.Eps; + } + + public int GetHashCode(float obj) + { + throw new InvalidOperationException(); + } + } + + protected void Print(string msg) + { + Debug.WriteLine(msg); + this.Output.WriteLine(msg); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs new file mode 100644 index 000000000..265d6b5f2 --- /dev/null +++ b/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs @@ -0,0 +1,144 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Formats.Jpg +{ + using System.Numerics; + using ImageSharp.Formats; + using Xunit; + using Xunit.Abstractions; + + public class ReferenceImplementationsTests : UtilityTestClassBase + { + public ReferenceImplementationsTests(ITestOutputHelper output) + : base(output) + { + } + + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) + { + MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); + MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); + + ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData); + + MutableSpan dest = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp); + + for (int i = 0; i < 64; i++) + { + float expected = intData[i]; + float actual = dest[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); + } + } + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void IntegerDCT_ForwardThenInverse(int seed, int startAt) + { + MutableSpan original = Create8x8RandomIntData(-200, 200, seed); + + var block = original.AddScalarToAllValues(128); + + ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block); + + for (int i = 0; i < 64; i++) + { + block[i] /= 8; + } + + ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block); + + for (int i = startAt; i < 64; i++) + { + float expected = original[i]; + float actual = (float)block[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); + } + + } + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt) + { + var data = Create8x8RandomIntData(-200, 200, seed); + MutableSpan src = new MutableSpan(data).ConvertToFloat32MutableSpan(); + MutableSpan dest = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.fDCT2D_llm(src, dest, temp, true); + ReferenceImplementations.iDCT2D_llm(dest, src, temp); + + for (int i = startAt; i < 64; i++) + { + float expected = data[i]; + float actual = (float)src[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); + } + } + + [Fact] + public void HowMuchIsTheFish() + { + Output.WriteLine(Vector.Count.ToString()); + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) + { + MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); + MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); + + ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData); + + MutableSpan dest = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); + + for (int i = 0; i < 64; i++) + { + float expected = intData[i]; + float actual = dest[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void Fdct_SimdReferenceImplementation_IsEquivalentToFloatingPointReferenceImplementation(int seed) + { + Block classic = new Block() { Data = Create8x8RandomIntData(-200, 200, seed) }; + MutableSpan src = new MutableSpan(classic.Data).ConvertToFloat32MutableSpan(); + + MutableSpan dest1 = new MutableSpan(64); + MutableSpan dest2 = new MutableSpan(64); + MutableSpan temp = new MutableSpan(64); + + ReferenceImplementations.fDCT2D_llm(src, dest1, temp, downscaleBy8: true, offsetSourceByNeg128: false); + ReferenceImplementations.fDCT8x8_llm_sse(src, dest2, temp); + + Assert.Equal(dest1.Data, dest2.Data, new ApproximateFloatComparer(1f)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj index 54227f025..2f03fa24c 100644 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -157,6 +157,7 @@ TestUtilities\TestUtilityExtensions.cs + From 421c224d1e492b653da60f20f97febfbb716876a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 02:34:49 +0100 Subject: [PATCH 03/15] removed duplicate IDCT implementation from Block8x8F --- .../Formats/Jpg/Components/Block8x8F.cs | 154 +----------------- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 2 +- 2 files changed, 3 insertions(+), 153 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 35d6044f5..4aace549d 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -51,23 +51,6 @@ namespace ImageSharp.Formats public Vector4 V7R; #pragma warning restore SA1600 // ElementsMustBeDocumented -#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore - private static readonly Vector4 C_1_175876 = new Vector4(1.175876f); - private static readonly Vector4 C_1_961571 = new Vector4(-1.961571f); - private static readonly Vector4 C_0_390181 = new Vector4(-0.390181f); - private static readonly Vector4 C_0_899976 = new Vector4(-0.899976f); - private static readonly Vector4 C_2_562915 = new Vector4(-2.562915f); - private static readonly Vector4 C_0_298631 = new Vector4(0.298631f); - private static readonly Vector4 C_2_053120 = new Vector4(2.053120f); - private static readonly Vector4 C_3_072711 = new Vector4(3.072711f); - private static readonly Vector4 C_1_501321 = new Vector4(1.501321f); - private static readonly Vector4 C_0_541196 = new Vector4(0.541196f); - private static readonly Vector4 C_1_847759 = new Vector4(-1.847759f); - private static readonly Vector4 C_0_765367 = new Vector4(0.765367f); - - private static readonly Vector4 C_0_125 = new Vector4(0.1250f); -#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore - /// /// Index into the block /// @@ -202,27 +185,7 @@ namespace ImageSharp.Formats this.V7L += diff; this.V7R += diff; } - - - /// - /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) - /// - /// Destination - /// Temporary block provided by the caller - public void TransformIDCTInto(ref Block8x8F dest, ref Block8x8F tempBlockPtr) - { - this.TransposeInto(ref tempBlockPtr); - tempBlockPtr.IDCT8x4_LeftPart(ref dest); - tempBlockPtr.IDCT8x4_RightPart(ref dest); - - dest.TransposeInto(ref tempBlockPtr); - - tempBlockPtr.IDCT8x4_LeftPart(ref dest); - tempBlockPtr.IDCT8x4_RightPart(ref dest); - - dest.MultiplyAllInplace(C_0_125); - } - + /// /// Pointer-based "Indexer" (getter part) /// @@ -300,120 +263,7 @@ namespace ImageSharp.Formats } } } - - /// - /// Do IDCT internal operations on the left part of the block. Original source: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// - /// Destination block - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void IDCT8x4_LeftPart(ref Block8x8F d) - { - Vector4 my1 = this.V1L; - Vector4 my7 = this.V7L; - Vector4 mz0 = my1 + my7; - - Vector4 my3 = this.V3L; - Vector4 mz2 = my3 + my7; - Vector4 my5 = this.V5L; - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = (mz0 + mz1) * C_1_175876; - - mz2 = (mz2 * C_1_961571) + mz4; - mz3 = (mz3 * C_0_390181) + mz4; - mz0 = mz0 * C_0_899976; - mz1 = mz1 * C_2_562915; - - Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; - Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; - Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; - Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; - - Vector4 my2 = this.V2L; - Vector4 my6 = this.V6L; - mz4 = (my2 + my6) * C_0_541196; - Vector4 my0 = this.V0L; - Vector4 my4 = this.V4L; - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * C_1_847759); - mz3 = mz4 + (my2 * C_0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - - d.V0L = my0 + mb0; - d.V7L = my0 - mb0; - d.V1L = my1 + mb1; - d.V6L = my1 - mb1; - d.V2L = my2 + mb2; - d.V5L = my2 - mb2; - d.V3L = my3 + mb3; - d.V4L = my3 - mb3; - } - - /// - /// Do IDCT internal operations on the right part of the block. - /// Original source: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// - /// Destination Block pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void IDCT8x4_RightPart(ref Block8x8F destBlockPtr) - { - Vector4 my1 = this.V1R; - Vector4 my7 = this.V7R; - Vector4 mz0 = my1 + my7; - - Vector4 my3 = this.V3R; - Vector4 mz2 = my3 + my7; - Vector4 my5 = this.V5R; - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = (mz0 + mz1) * C_1_175876; - - mz2 = (mz2 * C_1_961571) + mz4; - mz3 = (mz3 * C_0_390181) + mz4; - mz0 = mz0 * C_0_899976; - mz1 = mz1 * C_2_562915; - - Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; - Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; - Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; - Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; - - Vector4 my2 = this.V2R; - Vector4 my6 = this.V6R; - mz4 = (my2 + my6) * C_0_541196; - Vector4 my0 = this.V0R; - Vector4 my4 = this.V4R; - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * C_1_847759); - mz3 = mz4 + (my2 * C_0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - - destBlockPtr.V0R = my0 + mb0; - destBlockPtr.V7R = my0 - mb0; - destBlockPtr.V1R = my1 + mb1; - destBlockPtr.V6R = my1 - mb1; - destBlockPtr.V2R = my2 + mb2; - destBlockPtr.V5R = my2 - mb2; - destBlockPtr.V3R = my3 + mb3; - destBlockPtr.V4R = my3 - mb3; - } - + /// /// Fill the block with defaults (zeroes) /// diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index e302e064c..ca2090bee 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -1823,7 +1823,7 @@ namespace ImageSharp.Formats // Dequantize, perform the inverse DCT and store the block to the image. Block8x8F.UnZig(b, qt, unzigPtr); - b->TransformIDCTInto(ref *temp1, ref *temp2); + DCT.TransformIDCT(ref* b, ref *temp1, ref *temp2); byte[] dst; int offset; From 4f395391303e165416f1ab5161f9064c9403d480 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 03:01:49 +0100 Subject: [PATCH 04/15] finished re-applying existing encoder optimizations --- ImageSharp.sln.DotSettings | 1 + .../Formats/Jpg/Components/Block8x8F.cs | 48 +++ src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 31 +- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 375 +++++++++++------- src/ImageSharp/Formats/Jpg/JpegUtils.cs | 53 +++ src/ImageSharp/Formats/Jpg/UnzigData.cs | 38 ++ .../Formats/Jpg/Block8x8FTests.cs | 2 +- .../Formats/Jpg/ReferenceImplementations.cs | 2 +- .../Formats/Jpg/UtilityTestClassBase.cs | 2 +- .../ImageProviders/TestImageProvider.cs | 2 +- .../Jpg/ReferenceImplementationsTests.cs | 3 +- 11 files changed, 387 insertions(+), 170 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpg/JpegUtils.cs create mode 100644 src/ImageSharp/Formats/Jpg/UnzigData.cs diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index 22f46979a..fb607752a 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -340,6 +340,7 @@ </Patterns> True True + AC DC FDCT IDCT diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 4aace549d..c29591b6a 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -319,5 +319,53 @@ namespace ImageSharp.Formats src += 8; } } + + /// + /// Unzig the elements of src into dest, while dividing them by elements of qt and rounding the values + /// + /// Source block + /// Destination block + /// Quantization table + /// Pointer to elements + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void UnZigDivRound(Block8x8F* src, Block8x8F* dest, Block8x8F* qt, int* unzigPtr) + { + float* s = (float*)src; + float* d = (float*)dest; + float* q = (float*)qt; + + for (int zig = 0; zig < ScalarCount; zig++) + { + float val = s[unzigPtr[zig]] / q[zig]; + val = (int)val; + d[zig] = val; + } + } + + /// + /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block. + /// + /// The destination block. + /// The source block. + public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source) + { + float* d = (float*)destination; + for (int i = 0; i < 4; i++) + { + int dstOff = ((i & 2) << 4) | ((i & 1) << 2); + + float* iSource = (float*)(source + i); + + for (int y = 0; y < 4; y++) + { + for (int x = 0; x < 4; x++) + { + int j = (16 * y) + (2 * x); + float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9]; + d[(8 * y) + x + dstOff] = (sum + 2) / 4; + } + } + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index ca2090bee..c8f265ddf 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -62,20 +62,7 @@ namespace ImageSharp.Formats /// The AC table index /// private const int AcTable = 1; - - /// - /// Unzig maps from the zigzag ordering to the natural ordering. For example, - /// unzig[3] is the column and row of the fourth element in zigzag order. The - /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). - /// - private static readonly int[] Unzig = - { - 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, - 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, - 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - }; - + /// /// The component array /// @@ -1556,10 +1543,9 @@ namespace ImageSharp.Formats Block8x8F temp1 = default(Block8x8F); Block8x8F temp2 = default(Block8x8F); - // Tricky way to copy contents of the Unzig static variable to the stack: - StackallocUnzigData unzigOnStack = default(StackallocUnzigData); - int* unzigPtr = unzigOnStack.Data; - Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); + UnzigData unzig = UnzigData.Create(); + + int* unzigPtr = unzig.Data; for (int my = 0; my < myy; my++) { @@ -2013,7 +1999,7 @@ namespace ImageSharp.Formats int blah = zig; - zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta); + zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta, unzigPtr); if (zig > zigEnd) { throw new ImageFormatException($"Too many coefficients {zig} > {zigEnd}"); @@ -2030,7 +2016,7 @@ namespace ImageSharp.Formats if (this.eobRun > 0) { this.eobRun--; - this.RefineNonZeroes(b, zig, zigEnd, -1, delta); + this.RefineNonZeroes(b, zig, zigEnd, -1, delta, unzigPtr); } } @@ -2043,12 +2029,13 @@ namespace ImageSharp.Formats /// The zig-zag end index /// The non-zero entry /// The low transform offset + /// Pointer to the Jpeg Unzig data (data part of ) /// The - private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta) + private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta, int* unzigPtr) { for (; zig <= zigEnd; zig++) { - int u = Unzig[zig]; + int u = unzigPtr[zig]; float bu = Block8x8F.GetScalarAt(b, u); // TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary? diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index c1c4e9b57..548f237b7 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -7,7 +7,9 @@ namespace ImageSharp.Formats { using System; using System.IO; - + using System.Numerics; + using System.Runtime.CompilerServices; + /// /// Image encoder for writing an image to a stream as a jpeg. /// @@ -18,20 +20,8 @@ namespace ImageSharp.Formats /// private const int QuantizationTableCount = 2; - /// - /// Maps from the zig-zag ordering to the natural ordering. For example, - /// unzig[3] is the column and row of the fourth element in zig-zag order. The - /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). - /// - private static readonly int[] Unzig = - { - 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, - 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, - 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, - 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, - }; - #pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines + /// /// The Huffman encoding specifications. /// This encoder uses the same Huffman encoding for all images. @@ -44,10 +34,7 @@ namespace ImageSharp.Formats { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), new HuffmanSpec( new byte[] { @@ -122,7 +109,7 @@ namespace ImageSharp.Formats /// /// Counts the number of bits needed to hold an integer. /// - private readonly byte[] bitCountLut = + private static readonly uint[] BitCountLut = { 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, @@ -143,7 +130,7 @@ namespace ImageSharp.Formats /// The values are derived from section K.1 after converting from natural to /// zig-zag order. /// - private readonly byte[,] unscaledQuant = + private static readonly byte[,] UnscaledQuant = { { // Luminance. @@ -180,7 +167,10 @@ namespace ImageSharp.Formats /// /// The scaled quantization tables, in zig-zag order. /// - private readonly byte[][] quant = new byte[QuantizationTableCount][]; + //private readonly float[][] quant = new float[QuantizationTableCount][]; + //private readonly float[] quant = new float[QuantizationTableCount* Block8x8F.ScalarCount]; + private Block8x8F luminanceQuantTable; + private Block8x8F chrominanceQuantTable; /// /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: @@ -251,20 +241,26 @@ namespace ImageSharp.Formats /// LuminanceDC = 0, + // ReSharper disable UnusedMember.Local + /// /// The AC luminance huffman table index /// + LuminanceAC = 1, + /// /// The DC chrominance huffman table index /// ChrominanceDC = 2, - + /// /// The AC chrominance huffman table index /// ChrominanceAC = 3, + + // ReSharper restore UnusedMember.Local } /// @@ -283,6 +279,26 @@ namespace ImageSharp.Formats Chrominance = 1, } + private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) + { + for (int j = 0; j < Block8x8F.ScalarCount; j++) + { + int x = UnscaledQuant[i, j]; + x = ((x * scale) + 50) / 100; + if (x < 1) + { + x = 1; + } + + if (x > 255) + { + x = 255; + } + + quant[j] = x; + } + } + /// /// Encode writes the image to the jpeg baseline format with the given options. /// @@ -306,10 +322,10 @@ namespace ImageSharp.Formats this.outputStream = stream; this.subsample = sample; - for (int i = 0; i < QuantizationTableCount; i++) - { - this.quant[i] = new byte[Block.BlockSize]; - } + //for (int i = 0; i < QuantizationTableCount; i++) + //{ + // this.quant[i] = new float[]; + //} if (quality < 1) { @@ -333,26 +349,10 @@ namespace ImageSharp.Formats } // Initialize the quantization tables. - for (int i = 0; i < QuantizationTableCount; i++) - { - for (int j = 0; j < Block.BlockSize; j++) - { - int x = this.unscaledQuant[i, j]; - x = ((x * scale) + 50) / 100; - if (x < 1) - { - x = 1; - } - - if (x > 255) - { - x = 255; - } - - this.quant[i][j] = (byte)x; - } - } + InitQuantizationTable(0, scale, ref this.luminanceQuantTable); + InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); + // Compute number of components based on input image type. int componentCount = 3; @@ -382,23 +382,7 @@ namespace ImageSharp.Formats stream.Write(this.buffer, 0, 2); stream.Flush(); } - - /// - /// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero. - /// - /// The value to divide. - /// The value to divide by. - /// The - private static int Round(int dividend, int divisor) - { - if (dividend >= 0) - { - return (dividend + (divisor >> 1)) / divisor; - } - - return -((-dividend + (divisor >> 1)) / divisor); - } - + /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits < 1<<nBits && nBits <= 16. @@ -444,6 +428,7 @@ namespace ImageSharp.Formats /// /// The index of the Huffman encoder /// The value to encode. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EmitHuff(HuffIndex index, int value) { uint x = TheHuffmanLut[(int)index].Values[value]; @@ -456,6 +441,7 @@ namespace ImageSharp.Formats /// The index of the Huffman encoder /// The number of copies to encode. /// The value to encode. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EmitHuffRLE(HuffIndex index, int runLength, int value) { int a = value; @@ -469,11 +455,11 @@ namespace ImageSharp.Formats uint bt; if (a < 0x100) { - bt = this.bitCountLut[a]; + bt = BitCountLut[a]; } else { - bt = 8 + (uint)this.bitCountLut[a >> 8]; + bt = 8 + BitCountLut[a >> 8]; } this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); @@ -488,25 +474,39 @@ namespace ImageSharp.Formats /// returning the post-quantized DC value of the DCT-transformed block. /// The block is in natural (not zig-zag) order. /// - /// The block to write. /// The quantization table index. /// The previous DC value. + /// Source block + /// Temporal block to be used as FDCT Destination + /// Temporal block 2 + /// Quantization table + /// The 8x8 Unzig block ptr /// The - private int WriteBlock(ref Block block, QuantIndex index, int prevDC) + private float WriteBlock(QuantIndex index, float prevDC, Block8x8F* src, Block8x8F* tempDest, Block8x8F* temp2, Block8x8F* quant, int* unzigPtr) { - FDCT.Transform(ref block); + DCT.TransformFDCT(ref *src, ref *tempDest, ref *temp2); + + Block8x8F.UnZigDivRound(tempDest, temp2, quant, unzigPtr); + //Block8x8F.RoundAll(tempDest); + + float* d = (float*)temp2; + float* q = (float*)quant; // Emit the DC delta. - int dc = Round(block[0], 8 * this.quant[(int)index][0]); - this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC); + //float dc = Round(d[0], q[0]); + float dc = d[0]; + + this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, (int)(dc - prevDC)); + // Emit the AC components. HuffIndex h = (HuffIndex)((2 * (int)index) + 1); int runLength = 0; - + for (int zig = 1; zig < Block.BlockSize; zig++) { - int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]); + //float ac = Round(d[unzigPtr[zig]], q[zig]); + float ac = d[zig]; if (ac == 0) { @@ -520,7 +520,7 @@ namespace ImageSharp.Formats runLength -= 16; } - this.EmitHuffRLE(h, runLength, ac); + this.EmitHuffRLE(h, runLength, (int)ac); runLength = 0; } } @@ -543,58 +543,103 @@ namespace ImageSharp.Formats /// The luminance block. /// The red chroma block. /// The blue chroma block. - private void ToYCbCr(PixelAccessor pixels, int x, int y, ref Block yBlock, ref Block cbBlock, ref Block crBlock) + private static void ToYCbCr( + PixelAccessor pixels, + int x, + int y, + Block8x8F* yBlock, + Block8x8F* cbBlock, + Block8x8F* crBlock) where TColor : struct, IPackedPixel, IEquatable { + float* yBlockRaw = (float*)yBlock; + float* cbBlockRaw = (float*)cbBlock; + float* crBlockRaw = (float*)crBlock; + + PixelAccessor asStandardColorAccessor = pixels as PixelAccessor; + if (asStandardColorAccessor != null) + { + ColorRGBToYCbCr(asStandardColorAccessor, x, y, yBlockRaw, cbBlockRaw, crBlockRaw); + return; + } + + + Vector4 maxBytes = new Vector4(255f); + Vector4 half = new Vector4(0.5f); int xmax = pixels.Width - 1; int ymax = pixels.Height - 1; - byte[] color = new byte[3]; + for (int j = 0; j < 8; j++) { + int j8 = j * 8; for (int i = 0; i < 8; i++) { - pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToBytes(color, 0, ComponentOrder.XYZ); + Vector4 v = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToVector4(); + v = v * maxBytes + half; + + // Convert returned bytes into the YCbCr color space. Assume RGBA + float yy = ((0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z)); + float cb = (128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z))); + float cr = (128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z))); - byte r = color[0]; - byte g = color[1]; - byte b = color[2]; + int index = j8 + i; - // Convert returned bytes into the YCbCr color space. Assume RGBA - byte yy = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b)); - byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))); - byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))); - - int index = (8 * j) + i; - yBlock[index] = yy; - cbBlock[index] = cb; - crBlock[index] = cr; + yBlockRaw[index] = yy; + cbBlockRaw[index] = cb; + crBlockRaw[index] = cr; } } } - /// - /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 - /// DST block. - /// - /// The destination block array - /// The source block array. - private void Scale16X16To8X8(ref Block destination, Block[] source) + + + private static void ColorRGBToYCbCr( + PixelAccessor pixels, + int x, + int y, + float* yBlockRaw, + float* cbBlockRaw, + float* crBlockRaw) { - for (int i = 0; i < 4; i++) + int colorSize = sizeof(Color); + + int xmax = pixels.Width - 1; + int ymax = pixels.Height - 1; + + byte* data = (byte*)pixels.DataPointer; + + for (int j = 0; j < 8; j++) { - int dstOff = ((i & 2) << 4) | ((i & 1) << 2); - for (int y = 0; y < 4; y++) + int yPos = Math.Min(y + j, ymax); + + int j8 = j * 8; + for (int i = 0; i < 8; i++) { - for (int x = 0; x < 4; x++) - { - int j = (16 * y) + (2 * x); - int sum = source[i][j] + source[i][j + 1] + source[i][j + 8] + source[i][j + 9]; - destination[(8 * y) + x + dstOff] = (sum + 2) / 4; - } + int xPos = Math.Min(x + i, xmax); + + byte* dataPos = data + (((yPos * pixels.Width) + xPos) * colorSize); + Vector3 v = new Vector3(dataPos[0], dataPos[1], dataPos[2]); + + // Convert returned bytes into the YCbCr color space. Assume RGBA + float yy = ((0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z)); + float cb = (128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z))); + float cr = (128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z))); + + int index = j8 + i; + + yBlockRaw[index] = yy; + cbBlockRaw[index] = cb; + crBlockRaw[index] = cr; } } + + //(((y * pixels.Width) + x) * colorSize); + + } + + /// /// Writes the application header containing the JFIF identifier plus extra data. /// @@ -641,7 +686,7 @@ namespace ImageSharp.Formats /// The image. /// The pixel format. private void WriteProfiles(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPackedPixel, IEquatable { this.WriteProfile(image.ExifProfile); } @@ -691,19 +736,22 @@ namespace ImageSharp.Formats // This allows us to reduce the number of writes to the stream. byte[] dqt = new byte[(QuantizationTableCount * Block.BlockSize) + QuantizationTableCount]; int offset = 0; - for (int i = 0; i < QuantizationTableCount; i++) - { - dqt[offset++] = (byte)i; - int len = this.quant[i].Length; - for (int j = 0; j < len; j++) - { - dqt[offset++] = this.quant[i][j]; - } - } + WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); + WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); + this.outputStream.Write(dqt, 0, dqt.Length); } + private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) + { + dqt[offset++] = (byte)i; + for (int j = 0; j < Block8x8F.ScalarCount; j++) + { + dqt[offset++] = (byte)q[j]; + } + } + /// /// Writes the Start Of Frame (Baseline) marker /// @@ -842,27 +890,37 @@ namespace ImageSharp.Formats private void Encode444(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - Block b = Block.Create(); - Block cb = Block.Create(); - Block cr = Block.Create(); + Block8x8F b = new Block8x8F(); + Block8x8F cb = new Block8x8F(); + Block8x8F cr = new Block8x8F(); + + Block8x8F temp1 = new Block8x8F(); + Block8x8F temp2 = new Block8x8F(); + + Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; + Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; + + UnzigData unzig = UnzigData.Create(); // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; for (int y = 0; y < pixels.Height; y += 8) { for (int x = 0; x < pixels.Width; x += 8) { - this.ToYCbCr(pixels, x, y, ref b, ref cb, ref cr); - prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY); - prevDCCb = this.WriteBlock(ref cb, QuantIndex.Chrominance, prevDCCb); - prevDCCr = this.WriteBlock(ref cr, QuantIndex.Chrominance, prevDCCr); + ToYCbCr(pixels, x, y, &b, &cb, &cr); + + prevDCY = this.WriteBlock(QuantIndex.Luminance, prevDCY, &b, &temp1, &temp2, &onStackLuminanceQuantTable, unzig.Data); + prevDCCb = this.WriteBlock(QuantIndex.Chrominance, prevDCCb, &cb, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data); + prevDCCr = this.WriteBlock(QuantIndex.Chrominance, prevDCCr, &cr, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data); } } + } - b.Dispose(); - cb.Dispose(); - cr.Dispose(); + struct BlockQuad + { + public fixed float Data[4*Block8x8F.ScalarCount]; } /// @@ -874,12 +932,25 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - Block b = Block.Create(); - Block[] cb = Block.CreateArray(4); - Block[] cr = Block.CreateArray(4); + Block8x8F b = new Block8x8F(); + + + BlockQuad cb = new BlockQuad(); + BlockQuad cr = new BlockQuad(); + Block8x8F* cbPtr = (Block8x8F*)cb.Data; + Block8x8F* crPtr = (Block8x8F*)cr.Data; + + + Block8x8F temp1 = new Block8x8F(); + Block8x8F temp2 = new Block8x8F(); + + Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; + Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; + + UnzigData unzig = UnzigData.Create(); // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; for (int y = 0; y < pixels.Height; y += 16) { @@ -890,20 +961,38 @@ namespace ImageSharp.Formats int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]); - prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY); + ToYCbCr(pixels, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &b, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); } - this.Scale16X16To8X8(ref b, cb); - prevDCCb = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCb); - this.Scale16X16To8X8(ref b, cr); - prevDCCr = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCr); + Block8x8F.Scale16X16To8X8(&b, cbPtr); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + Block8x8F.Scale16X16To8X8(&b, crPtr); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); } } - - b.Dispose(); - Block.DisposeAll(cb); - Block.DisposeAll(cr); } /// @@ -956,6 +1045,11 @@ namespace ImageSharp.Formats /// private class HuffmanLut { + /// + /// The collection of huffman values. + /// + public uint[] Values { get; } + /// /// Initializes a new instance of the class. /// @@ -990,11 +1084,6 @@ namespace ImageSharp.Formats code <<= 1; } } - - /// - /// Gets the collection of huffman values. - /// - public uint[] Values { get; } } } } diff --git a/src/ImageSharp/Formats/Jpg/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/JpegUtils.cs new file mode 100644 index 000000000..564ab8655 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/JpegUtils.cs @@ -0,0 +1,53 @@ +namespace ImageSharp.Formats +{ + using System; + using System.Runtime.CompilerServices; + + internal static unsafe class JpegUtils + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void CopyRgb(byte* source, byte* dest) + { + *dest++ = *source++; // R + *dest++ = *source++; // G + *dest = *source; // B + } + + internal static unsafe void RepeatPixelsBottomRight(PixelArea area, int fromX, int fromY) + where TColor : struct, IPackedPixel, IEquatable + { + if (fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height) + { + throw new InvalidOperationException(); + } + + for (int y = 0; y < fromY; y++) + { + byte* ptrBase = (byte*)area.DataPointer + y * area.RowByteCount; + + for (int x = fromX; x < area.Width; x++) + { + byte* prevPtr = ptrBase + (x - 1) * 3; + byte* currPtr = ptrBase + x * 3; + + CopyRgb(prevPtr, currPtr); + } + } + + for (int y = fromY; y < area.Height; y++) + { + byte* currBase = (byte*)area.DataPointer + y * area.RowByteCount; + byte* prevBase = (byte*)area.DataPointer + (y - 1) * area.RowByteCount; + + for (int x = 0; x < area.Width; x++) + { + int x3 = 3 * x; + byte* currPtr = currBase + x3; + byte* prevPtr = prevBase + x3; + + CopyRgb(prevPtr, currPtr); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/UnzigData.cs b/src/ImageSharp/Formats/Jpg/UnzigData.cs new file mode 100644 index 000000000..6dae6d942 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/UnzigData.cs @@ -0,0 +1,38 @@ +namespace ImageSharp.Formats +{ + using System; + using System.Runtime.InteropServices; + + /// + /// Holds the Jpeg UnZig array in a value/stack type. + /// Unzig maps from the zigzag ordering to the natural ordering. For example, + /// unzig[3] is the column and row of the fourth element in zigzag order. The + /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// + internal unsafe struct UnzigData + { + internal fixed int Data[64]; + + public static UnzigData Create() + { + UnzigData result = new UnzigData(); + int* unzigPtr = result.Data; + Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); + return result; + } + + /// + /// Unzig maps from the zigzag ordering to the natural ordering. For example, + /// unzig[3] is the column and row of the fourth element in zigzag order. The + /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// + private static readonly int[] Unzig = + { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, + 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, + 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + }; + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 8939d2c20..7d66f6347 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -3,7 +3,7 @@ // ReSharper disable InconsistentNaming -namespace ImageSharp.Tests.Formats.Jpg +namespace ImageSharp.Tests { using System.Diagnostics; using System.Numerics; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index 006e95963..860c1b55e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs @@ -1,6 +1,6 @@ // ReSharper disable InconsistentNaming -namespace ImageSharp.Tests.Formats.Jpg +namespace ImageSharp.Tests { using System; using System.Numerics; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs index 20a9d34b1..6191d221c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs @@ -3,7 +3,7 @@ using ImageSharp.Formats; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace ImageSharp.Tests.Formats.Jpg +namespace ImageSharp.Tests { using System; using System.Collections.Generic; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 8a8db3ee8..be5559e2a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -80,7 +80,7 @@ namespace ImageSharp.Tests this.Utility = new ImagingTestCaseUtility() { SourceFileOrDescription = this.SourceFileOrDescription, - PixelTypeName = typeof(TColor).Name + PixelTypeName = this.PixelType.ToString() }; if (testMethod != null) diff --git a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs index 265d6b5f2..e30baddfc 100644 --- a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs @@ -1,8 +1,9 @@ // ReSharper disable InconsistentNaming -namespace ImageSharp.Tests.Formats.Jpg +namespace ImageSharp.Tests { using System.Numerics; using ImageSharp.Formats; + using Xunit; using Xunit.Abstractions; From c3628ab11991f7c08d1f3fe4bc89b44d4d5d05db Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 04:09:04 +0100 Subject: [PATCH 05/15] CopyRGBBytesStretchedTo --- src/ImageSharp/Formats/Jpg/Components/DCT.cs | 52 +++++++--- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 17 ++-- src/ImageSharp/Formats/Jpg/JpegUtils.cs | 25 ++++- .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 98 +++++++++++++++++++ 4 files changed, 162 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/DCT.cs b/src/ImageSharp/Formats/Jpg/Components/DCT.cs index a4763a8f5..6b2344b39 100644 --- a/src/ImageSharp/Formats/Jpg/Components/DCT.cs +++ b/src/ImageSharp/Formats/Jpg/Components/DCT.cs @@ -3,13 +3,14 @@ // Licensed under the Apache License, Version 2.0. // // ReSharper disable InconsistentNaming + namespace ImageSharp.Formats { using System.Numerics; using System.Runtime.CompilerServices; /// - /// Contains forward & inverse DCT implementations + /// Contains forward and inverse DCT implementations /// internal static class DCT { @@ -35,16 +36,27 @@ namespace ImageSharp.Formats #pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore private static readonly Vector4 C_1_175876 = new Vector4(1.175876f); + private static readonly Vector4 C_1_961571 = new Vector4(-1.961571f); + private static readonly Vector4 C_0_390181 = new Vector4(-0.390181f); + private static readonly Vector4 C_0_899976 = new Vector4(-0.899976f); + private static readonly Vector4 C_2_562915 = new Vector4(-2.562915f); + private static readonly Vector4 C_0_298631 = new Vector4(0.298631f); + private static readonly Vector4 C_2_053120 = new Vector4(2.053120f); + private static readonly Vector4 C_3_072711 = new Vector4(3.072711f); + private static readonly Vector4 C_1_501321 = new Vector4(1.501321f); + private static readonly Vector4 C_0_541196 = new Vector4(0.541196f); + private static readonly Vector4 C_1_847759 = new Vector4(-1.847759f); + private static readonly Vector4 C_0_765367 = new Vector4(0.765367f); private static readonly Vector4 C_0_125 = new Vector4(0.1250f); @@ -52,7 +64,7 @@ namespace ImageSharp.Formats private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); /// - /// Do IDCT internal operations on the left part of the block. Original source: + /// Do IDCT internal operations on the left part of the block. Original src: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 /// /// Destination block @@ -109,7 +121,7 @@ namespace ImageSharp.Formats /// /// Do IDCT internal operations on the right part of the block. - /// Original source: + /// Original src: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -163,7 +175,6 @@ namespace ImageSharp.Formats d.V4R = my3 - mb3; } - /// /// Original: /// @@ -220,7 +231,6 @@ namespace ImageSharp.Formats Vector4 w0 = new Vector4(0.541196f); Vector4 w1 = new Vector4(1.306563f); - d.V2L = (w0 * c2) + (w1 * c3); d.V6L = (w0 * c3) - (w1 * c2); @@ -325,7 +335,6 @@ namespace ImageSharp.Formats Vector4 w0 = new Vector4(0.541196f); Vector4 w1 = new Vector4(1.306563f); - d.V2R = (w0 * c2) + (w1 * c3); d.V6R = (w0 * c3) - (w1 * c2); @@ -373,23 +382,34 @@ namespace ImageSharp.Formats }*/ } - public static void TransformFDCT(ref Block8x8F s, ref Block8x8F d, ref Block8x8F temp, bool offsetSourceByNeg128 = true) + /// + /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) + /// + /// Source + /// Destination + /// Temporary block provided by the caller + /// If true, a constant -128.0 offset is applied for all values before FDCT + public static void TransformFDCT( + ref Block8x8F src, + ref Block8x8F dest, + ref Block8x8F temp, + bool offsetSourceByNeg128 = true) { - s.TransposeInto(ref temp); + src.TransposeInto(ref temp); if (offsetSourceByNeg128) { temp.AddToAllInplace(new Vector4(-128)); } - FDCT8x4_LeftPart(ref temp, ref d); - FDCT8x4_RightPart(ref temp, ref d); + FDCT8x4_LeftPart(ref temp, ref dest); + FDCT8x4_RightPart(ref temp, ref dest); + + dest.TransposeInto(ref temp); + + FDCT8x4_LeftPart(ref temp, ref dest); + FDCT8x4_RightPart(ref temp, ref dest); - d.TransposeInto(ref temp); - - FDCT8x4_LeftPart(ref temp, ref d); - FDCT8x4_RightPart(ref temp, ref d); - - d.MultiplyAllInplace(C_0_125); + dest.MultiplyAllInplace(C_0_125); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 548f237b7..0010ff98f 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -34,7 +34,10 @@ namespace ImageSharp.Formats { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), new HuffmanSpec( new byte[] { @@ -562,8 +565,7 @@ namespace ImageSharp.Formats ColorRGBToYCbCr(asStandardColorAccessor, x, y, yBlockRaw, cbBlockRaw, crBlockRaw); return; } - - + Vector4 maxBytes = new Vector4(255f); Vector4 half = new Vector4(0.5f); int xmax = pixels.Width - 1; @@ -590,9 +592,8 @@ namespace ImageSharp.Formats } } } - - + // ReSharper disable once InconsistentNaming private static void ColorRGBToYCbCr( PixelAccessor pixels, int x, @@ -632,10 +633,6 @@ namespace ImageSharp.Formats crBlockRaw[index] = cr; } } - - //(((y * pixels.Width) + x) * colorSize); - - } @@ -934,13 +931,11 @@ namespace ImageSharp.Formats { Block8x8F b = new Block8x8F(); - BlockQuad cb = new BlockQuad(); BlockQuad cr = new BlockQuad(); Block8x8F* cbPtr = (Block8x8F*)cb.Data; Block8x8F* crPtr = (Block8x8F*)cr.Data; - Block8x8F temp1 = new Block8x8F(); Block8x8F temp2 = new Block8x8F(); diff --git a/src/ImageSharp/Formats/Jpg/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/JpegUtils.cs index 564ab8655..f881a4088 100644 --- a/src/ImageSharp/Formats/Jpg/JpegUtils.cs +++ b/src/ImageSharp/Formats/Jpg/JpegUtils.cs @@ -13,12 +13,12 @@ *dest = *source; // B } - internal static unsafe void RepeatPixelsBottomRight(PixelArea area, int fromX, int fromY) + private static unsafe void StretchPixels(PixelArea area, int fromX, int fromY) where TColor : struct, IPackedPixel, IEquatable { - if (fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height) + if (IsInvalidStretchArea(area, fromX, fromY)) { - throw new InvalidOperationException(); + return; } for (int y = 0; y < fromY; y++) @@ -49,5 +49,24 @@ } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsInvalidStretchArea(PixelArea area, int fromX, int fromY) where TColor : struct, IPackedPixel, IEquatable + { + return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; + } + + public static void CopyRGBBytesStretchedTo( + this PixelAccessor pixels, + PixelArea dest, + int sourceY, + int sourceX) + where TColor : struct, IPackedPixel, IEquatable + { + pixels.CopyTo(dest, sourceY, sourceX); + int stretchFromX = pixels.Width - sourceX; + int stretchFromY = pixels.Height - sourceY; + StretchPixels(dest, stretchFromX, stretchFromY); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 6a9267fec..fb1ca9371 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -5,9 +5,12 @@ using System.Linq; using ImageSharp.Formats; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace ImageSharp.Tests { + using System.Numerics; + public class JpegTests { private ITestOutputHelper Output { get; } @@ -53,5 +56,100 @@ namespace ImageSharp.Tests image.Save(outputStream, encoder); } } + + public static Image CreateTestImage(GenericFactory factory) + where TColor : struct, IPackedPixel, IEquatable + { + Image image = factory.CreateImage(10, 10); + + using (var pixels = image.Lock()) + { + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + Vector4 v = new Vector4(i/10f, j/10f, 0, 1); + + TColor color = default(TColor); + color.PackFromVector4(v); + + pixels[i, j] = color; + } + } + } + return image; + } + + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] + public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + var src = provider.GetImage(); + + PixelArea area = new PixelArea(8, 8, ComponentOrder.XYZ); + var dest = provider.Factory.CreateImage(8, 8); + + using (var s = src.Lock()) + { + using (var d = dest.Lock()) + { + s.CopyRGBBytesStretchedTo(area, 0, 0); + d.CopyFrom(area, 0, 0); + + Assert.Equal(s[0, 0], d[0, 0]); + Assert.Equal(s[7, 0], d[7, 0]); + Assert.Equal(s[0, 7], d[0, 7]); + Assert.Equal(s[7, 7], d[7, 7]); + } + } + } + + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] + public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + var src = provider.GetImage(); + + PixelArea area = new PixelArea(8, 8, ComponentOrder.XYZ); + var dest = provider.Factory.CreateImage(8, 8); + + using (var s = src.Lock()) + { + using (var d = dest.Lock()) + { + s.CopyRGBBytesStretchedTo(area, 7, 6); + d.CopyFrom(area, 0, 0); + + Assert.Equal(s[6, 7], d[0, 0]); + Assert.Equal(s[6, 8], d[0, 1]); + Assert.Equal(s[7, 8], d[1, 1]); + + Assert.Equal(s[6, 9], d[0, 2]); + Assert.Equal(s[6, 9], d[0, 3]); + Assert.Equal(s[6, 9], d[0, 7]); + + Assert.Equal(s[7, 9], d[1, 2]); + Assert.Equal(s[7, 9], d[1, 3]); + Assert.Equal(s[7, 9], d[1, 7]); + + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[3, 3]); + Assert.Equal(s[9, 9], d[3, 7]); + + Assert.Equal(s[9, 7], d[3, 0]); + Assert.Equal(s[9, 7], d[4, 0]); + Assert.Equal(s[9, 7], d[7, 0]); + + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[4, 2]); + Assert.Equal(s[9, 9], d[7, 2]); + + Assert.Equal(s[9, 9], d[4, 3]); + Assert.Equal(s[9, 9], d[7, 7]); + } + } + } } } \ No newline at end of file From 1e05986ddb3e2719fc57954f4513d31ae4a459e8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 05:58:14 +0100 Subject: [PATCH 06/15] StyleCop :S --- .../Formats/Jpg/Components/Block8x8F.cs | 33 +- src/ImageSharp/Formats/Jpg/Components/DCT.cs | 219 +++------ .../Jpg/Components/MutableSpanExtensions.cs | 36 +- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 4 +- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 433 +++++++++--------- src/ImageSharp/Formats/Jpg/JpegUtils.cs | 63 ++- src/ImageSharp/Formats/Jpg/UnzigData.cs | 30 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 6 +- src/ImageSharp/PixelAccessor.cs | 20 + .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 28 ++ .../ImageProviders/TestImageProvider.cs | 6 + 11 files changed, 453 insertions(+), 425 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index c29591b6a..89b838289 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -3,6 +3,7 @@ // Licensed under the Apache License, Version 2.0. // // ReSharper disable InconsistentNaming + namespace ImageSharp.Formats { using System; @@ -15,6 +16,7 @@ namespace ImageSharp.Formats /// internal partial struct Block8x8F { +#pragma warning disable SA1204 // Static members must appear before non-static members /// /// Vector count /// @@ -141,7 +143,7 @@ namespace ImageSharp.Formats } /// - /// Multiply in place + /// Multiply all elements of the block. /// /// Vector to multiply by [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -165,6 +167,10 @@ namespace ImageSharp.Formats this.V7R *= scaleVec; } + /// + /// Adds a vector to all elements of the block. + /// + /// The added vector [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddToAllInplace(Vector4 diff) { @@ -185,7 +191,7 @@ namespace ImageSharp.Formats this.V7L += diff; this.V7R += diff; } - + /// /// Pointer-based "Indexer" (getter part) /// @@ -193,7 +199,7 @@ namespace ImageSharp.Formats /// Index /// The scaleVec value at the specified index [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx) + public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx) { float* fp = (float*)blockPtr; return fp[idx]; @@ -206,7 +212,7 @@ namespace ImageSharp.Formats /// Index /// Value [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value) + public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value) { float* fp = (float*)blockPtr; fp[idx] = value; @@ -219,7 +225,7 @@ namespace ImageSharp.Formats /// Qt pointer /// Unzig pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void UnZig(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) + public static unsafe void UnZig(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) { float* b = (float*)blockPtr; float* qtp = (float*)qtPtr; @@ -236,7 +242,7 @@ namespace ImageSharp.Formats /// Copy raw 32bit floating point data to dest /// /// Destination - internal unsafe void CopyTo(MutableSpan dest) + public unsafe void CopyTo(MutableSpan dest) { fixed (Vector4* ptr = &this.V0L) { @@ -252,7 +258,7 @@ namespace ImageSharp.Formats /// Load raw 32bit floating point data from source /// /// Source - internal unsafe void LoadFrom(MutableSpan source) + public unsafe void LoadFrom(MutableSpan source) { fixed (Vector4* ptr = &this.V0L) { @@ -263,12 +269,12 @@ namespace ImageSharp.Formats } } } - + /// /// Fill the block with defaults (zeroes) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Clear() + public void Clear() { // The cheapest way to do this in C#: this = default(Block8x8F); @@ -278,7 +284,7 @@ namespace ImageSharp.Formats /// TODO: Should be removed when BlockF goes away /// /// Legacy block - internal void LoadFrom(ref BlockF legacyBlock) + public void LoadFrom(ref BlockF legacyBlock) { this.LoadFrom(legacyBlock.Data); } @@ -287,12 +293,11 @@ namespace ImageSharp.Formats /// TODO: Should be removed when BlockF goes away /// /// Legacy block - internal void CopyTo(ref BlockF legacyBlock) + public void CopyTo(ref BlockF legacyBlock) { this.CopyTo(legacyBlock.Data); } - /// /// Level shift by +128, clip to [0, 255], and write to buffer. /// @@ -300,7 +305,7 @@ namespace ImageSharp.Formats /// Stride offset /// Temp Block pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal unsafe void CopyColorsTo(MutableSpan buffer, int stride, Block8x8F* tempBlockPtr) + public unsafe void CopyColorsTo(MutableSpan buffer, int stride, Block8x8F* tempBlockPtr) { this.TransformByteConvetibleColorValuesInto(ref *tempBlockPtr); @@ -328,7 +333,7 @@ namespace ImageSharp.Formats /// Quantization table /// Pointer to elements [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe void UnZigDivRound(Block8x8F* src, Block8x8F* dest, Block8x8F* qt, int* unzigPtr) + public static unsafe void UnZigDivRound(Block8x8F* src, Block8x8F* dest, Block8x8F* qt, int* unzigPtr) { float* s = (float*)src; float* d = (float*)dest; diff --git a/src/ImageSharp/Formats/Jpg/Components/DCT.cs b/src/ImageSharp/Formats/Jpg/Components/DCT.cs index 6b2344b39..a207df670 100644 --- a/src/ImageSharp/Formats/Jpg/Components/DCT.cs +++ b/src/ImageSharp/Formats/Jpg/Components/DCT.cs @@ -14,26 +14,6 @@ namespace ImageSharp.Formats /// internal static class DCT { - /// - /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) - /// - /// Source - /// Destination - /// Temporary block provided by the caller - public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) - { - src.TransposeInto(ref temp); - IDCT8x4_LeftPart(ref temp, ref dest); - IDCT8x4_RightPart(ref temp, ref dest); - - dest.TransposeInto(ref temp); - - IDCT8x4_LeftPart(ref temp, ref dest); - IDCT8x4_RightPart(ref temp, ref dest); - - dest.MultiplyAllInplace(C_0_125); - } - #pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore private static readonly Vector4 C_1_175876 = new Vector4(1.175876f); @@ -63,13 +43,34 @@ namespace ImageSharp.Formats #pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); + /// + /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) + /// + /// Source + /// Destination + /// Temporary block provided by the caller + public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) + { + src.TransposeInto(ref temp); + IDCT8x4_LeftPart(ref temp, ref dest); + IDCT8x4_RightPart(ref temp, ref dest); + + dest.TransposeInto(ref temp); + + IDCT8x4_LeftPart(ref temp, ref dest); + IDCT8x4_RightPart(ref temp, ref dest); + + dest.MultiplyAllInplace(C_0_125); + } + /// /// Do IDCT internal operations on the left part of the block. Original src: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 /// + /// The source block /// Destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) + public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) { Vector4 my1 = s.V1L; Vector4 my7 = s.V7L; @@ -81,7 +82,7 @@ namespace ImageSharp.Formats Vector4 mz1 = my3 + my5; Vector4 mz3 = my1 + my5; - Vector4 mz4 = ((mz0 + mz1) * C_1_175876); + Vector4 mz4 = (mz0 + mz1) * C_1_175876; mz2 = (mz2 * C_1_961571) + mz4; mz3 = (mz3 * C_0_390181) + mz4; @@ -124,8 +125,10 @@ namespace ImageSharp.Formats /// Original src: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 /// + /// The source block + /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) + public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) { Vector4 my1 = s.V1R; Vector4 my7 = s.V7R; @@ -187,96 +190,57 @@ namespace ImageSharp.Formats { Vector4 c0 = s.V0L; Vector4 c1 = s.V7L; - Vector4 t0 = (c0 + c1); - Vector4 t7 = (c0 - c1); + Vector4 t0 = c0 + c1; + Vector4 t7 = c0 - c1; c1 = s.V6L; c0 = s.V1L; - Vector4 t1 = (c0 + c1); - Vector4 t6 = (c0 - c1); + Vector4 t1 = c0 + c1; + Vector4 t6 = c0 - c1; c1 = s.V5L; c0 = s.V2L; - Vector4 t2 = (c0 + c1); - Vector4 t5 = (c0 - c1); + Vector4 t2 = c0 + c1; + Vector4 t5 = c0 - c1; c0 = s.V3L; c1 = s.V4L; - Vector4 t3 = (c0 + c1); - Vector4 t4 = (c0 - c1); - - /* - c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; - c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; - c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; - c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; - */ - - c0 = (t0 + t3); - Vector4 c3 = (t0 - t3); - c1 = (t1 + t2); - Vector4 c2 = (t1 - t2); - - /* - c0 = t0 + t3; c3 = t0 - t3; - c1 = t1 + t2; c2 = t1 - t2; - */ + Vector4 t3 = c0 + c1; + Vector4 t4 = c0 - c1; + + c0 = t0 + t3; + Vector4 c3 = t0 - t3; + c1 = t1 + t2; + Vector4 c2 = t1 - t2; d.V0L = c0 + c1; d.V4L = c0 - c1; - /*y[0] = c0 + c1; - y[4] = c0 - c1;*/ - Vector4 w0 = new Vector4(0.541196f); Vector4 w1 = new Vector4(1.306563f); d.V2L = (w0 * c2) + (w1 * c3); - d.V6L = (w0 * c3) - (w1 * c2); - /* - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - */ w0 = new Vector4(1.175876f); w1 = new Vector4(0.785695f); - c3 = ((w0 * t4) + (w1 * t7)); - c0 = ((w0 * t7) - (w1 * t4)); - /* - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - */ + c3 = (w0 * t4) + (w1 * t7); + c0 = (w0 * t7) - (w1 * t4); w0 = new Vector4(1.387040f); w1 = new Vector4(0.275899f); - c2 = ((w0 * t5) + (w1 * t6)); - c1 = ((w0 * t6) - (w1 * t5)); - /* - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; - */ + c2 = (w0 * t5) + (w1 * t6); + c1 = (w0 * t6) - (w1 * t5); - d.V3L = (c0 - c2); - - d.V5L = (c3 - c1); - //y[5] = c3 - c1; y[3] = c0 - c2; + d.V3L = c0 - c2; + d.V5L = c3 - c1; Vector4 invsqrt2 = new Vector4(0.707107f); - c0 = ((c0 + c2) * invsqrt2); - c3 = ((c3 + c1) * invsqrt2); - //c0 = (c0 + c2) * invsqrt2; - //c3 = (c3 + c1) * invsqrt2; - - d.V1L = (c0 + c3); - - d.V7L = (c0 - c3); - //y[1] = c0 + c3; y[7] = c0 - c3; + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; - /*for(i = 0;i < 8;i++) - { - y[i] *= invsqrt2h; - }*/ + d.V1L = c0 + c3; + d.V7L = c0 - c3; } /// @@ -291,95 +255,56 @@ namespace ImageSharp.Formats { Vector4 c0 = s.V0R; Vector4 c1 = s.V7R; - Vector4 t0 = (c0 + c1); - Vector4 t7 = (c0 - c1); + Vector4 t0 = c0 + c1; + Vector4 t7 = c0 - c1; c1 = s.V6R; c0 = s.V1R; - Vector4 t1 = (c0 + c1); - Vector4 t6 = (c0 - c1); + Vector4 t1 = c0 + c1; + Vector4 t6 = c0 - c1; c1 = s.V5R; c0 = s.V2R; - Vector4 t2 = (c0 + c1); - Vector4 t5 = (c0 - c1); + Vector4 t2 = c0 + c1; + Vector4 t5 = c0 - c1; c0 = s.V3R; c1 = s.V4R; - Vector4 t3 = (c0 + c1); - Vector4 t4 = (c0 - c1); - - /* - c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; - c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; - c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; - c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; - */ - - c0 = (t0 + t3); - Vector4 c3 = (t0 - t3); - c1 = (t1 + t2); - Vector4 c2 = (t1 - t2); - - /* - c0 = t0 + t3; c3 = t0 - t3; - c1 = t1 + t2; c2 = t1 - t2; - */ + Vector4 t3 = c0 + c1; + Vector4 t4 = c0 - c1; + + c0 = t0 + t3; + Vector4 c3 = t0 - t3; + c1 = t1 + t2; + Vector4 c2 = t1 - t2; d.V0R = c0 + c1; d.V4R = c0 - c1; - /*y[0] = c0 + c1; - y[4] = c0 - c1;*/ - Vector4 w0 = new Vector4(0.541196f); Vector4 w1 = new Vector4(1.306563f); d.V2R = (w0 * c2) + (w1 * c3); - d.V6R = (w0 * c3) - (w1 * c2); - /* - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - */ w0 = new Vector4(1.175876f); w1 = new Vector4(0.785695f); - c3 = ((w0 * t4) + (w1 * t7)); - c0 = ((w0 * t7) - (w1 * t4)); - /* - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - */ + c3 = (w0 * t4) + (w1 * t7); + c0 = (w0 * t7) - (w1 * t4); w0 = new Vector4(1.387040f); w1 = new Vector4(0.275899f); - c2 = ((w0 * t5) + (w1 * t6)); - c1 = ((w0 * t6) - (w1 * t5)); - /* - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; - */ - - d.V3R = (c0 - c2); - - d.V5R = (c3 - c1); - //y[5] = c3 - c1; y[3] = c0 - c2; - - c0 = ((c0 + c2) * InvSqrt2); - c3 = ((c3 + c1) * InvSqrt2); - //c0 = (c0 + c2) * invsqrt2; - //c3 = (c3 + c1) * invsqrt2; + c2 = (w0 * t5) + (w1 * t6); + c1 = (w0 * t6) - (w1 * t5); - d.V1R = (c0 + c3); + d.V3R = c0 - c2; + d.V5R = c3 - c1; - d.V7R = (c0 - c3); - //y[1] = c0 + c3; y[7] = c0 - c3; + c0 = (c0 + c2) * InvSqrt2; + c3 = (c3 + c1) * InvSqrt2; - /*for(i = 0;i < 8;i++) - { - y[i] *= invsqrt2h; - }*/ + d.V1R = c0 + c3; + d.V7R = c0 - c3; } /// diff --git a/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs b/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs index 7253a816e..31df4863c 100644 --- a/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs +++ b/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs @@ -78,8 +78,11 @@ namespace ImageSharp.Formats data[3] = (int)v.W; } - - + /// + /// Converts all int values of src to float + /// + /// Source + /// A new with float values public static MutableSpan ConvertToFloat32MutableSpan(this MutableSpan src) { MutableSpan result = new MutableSpan(src.TotalCount); @@ -87,9 +90,15 @@ namespace ImageSharp.Formats { result[i] = (float)src[i]; } + return result; } + /// + /// Converts all float values of src to int + /// + /// Source + /// A new with float values public static MutableSpan ConvertToInt32MutableSpan(this MutableSpan src) { MutableSpan result = new MutableSpan(src.TotalCount); @@ -97,9 +106,16 @@ namespace ImageSharp.Formats { result[i] = (int)src[i]; } + return result; } + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of public static MutableSpan AddScalarToAllValues(this MutableSpan src, float scalar) { MutableSpan result = new MutableSpan(src.TotalCount); @@ -107,9 +123,16 @@ namespace ImageSharp.Formats { result[i] = src[i] + scalar; } + return result; } + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of public static MutableSpan AddScalarToAllValues(this MutableSpan src, int scalar) { MutableSpan result = new MutableSpan(src.TotalCount); @@ -117,10 +140,16 @@ namespace ImageSharp.Formats { result[i] = src[i] + scalar; } + return result; } - + /// + /// Copy all values in src to a new instance + /// + /// Element type + /// The source + /// A new instance of public static MutableSpan Copy(this MutableSpan src) { MutableSpan result = new MutableSpan(src.TotalCount); @@ -128,6 +157,7 @@ namespace ImageSharp.Formats { result[i] = src[i]; } + return result; } } diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index c8f265ddf..960405530 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -62,7 +62,7 @@ namespace ImageSharp.Formats /// The AC table index /// private const int AcTable = 1; - + /// /// The component array /// @@ -1809,7 +1809,7 @@ namespace ImageSharp.Formats // Dequantize, perform the inverse DCT and store the block to the image. Block8x8F.UnZig(b, qt, unzigPtr); - DCT.TransformIDCT(ref* b, ref *temp1, ref *temp2); + DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2); byte[] dst; int offset; diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 0010ff98f..4444e50bf 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Formats using System.IO; using System.Numerics; using System.Runtime.CompilerServices; - + /// /// Image encoder for writing an image to a stream as a jpeg. /// @@ -113,19 +113,21 @@ namespace ImageSharp.Formats /// Counts the number of bits needed to hold an integer. /// private static readonly uint[] BitCountLut = - { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, - }; + { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, + }; /// /// The unscaled quantization tables in zig-zag order. Each @@ -134,23 +136,53 @@ namespace ImageSharp.Formats /// zig-zag order. /// private static readonly byte[,] UnscaledQuant = - { { - // Luminance. - 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, - 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, - 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, - 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, - 101, 103, 99, - }, + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, + 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, + 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, + 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, + 100, 120, 92, 101, 103, 99, + }, + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + } + }; + + /// + /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: + /// - the marker length "\x00\x0c", + /// - the number of components "\x03", + /// - component 1 uses DC table 0 and AC table 0 "\x01\x00", + /// - component 2 uses DC table 1 and AC table 1 "\x02\x11", + /// - component 3 uses DC table 1 and AC table 1 "\x03\x11", + /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + /// should be 0x00, 0x3f, 0x00<<4 | 0x00. + /// + private static readonly byte[] SosHeaderYCbCr = { - // Chrominance. - 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - } - }; + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, // Marker + 0x00, 0x0c, + + // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + 0x03, // Number of components in a scan, 3 + 0x01, // Component Id Y + 0x00, // DC/AC Huffman table + 0x02, // Component Id Cb + 0x11, // DC/AC Huffman table + 0x03, // Component Id Cr + 0x11, // DC/AC Huffman table + 0x00, // Ss - Start of spectral selection. + 0x3f, // Se - End of spectral selection. + 0x00 // Ah + Ah (Successive approximation bit position high + low) + }; /// /// A scratch buffer to reduce allocations. @@ -168,39 +200,14 @@ namespace ImageSharp.Formats private readonly byte[] huffmanBuffer = new byte[179]; /// - /// The scaled quantization tables, in zig-zag order. + /// The scaled luminance table, in zig-zag order. /// - //private readonly float[][] quant = new float[QuantizationTableCount][]; - //private readonly float[] quant = new float[QuantizationTableCount* Block8x8F.ScalarCount]; private Block8x8F luminanceQuantTable; - private Block8x8F chrominanceQuantTable; /// - /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: - /// - the marker length "\x00\x0c", - /// - the number of components "\x03", - /// - component 1 uses DC table 0 and AC table 0 "\x01\x00", - /// - component 2 uses DC table 1 and AC table 1 "\x02\x11", - /// - component 3 uses DC table 1 and AC table 1 "\x03\x11", - /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - /// should be 0x00, 0x3f, 0x00<<4 | 0x00. + /// The scaled chrominance table, in zig-zag order. /// - private readonly byte[] sosHeaderYCbCr = - { - JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, // Marker - 0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) - 0x03, // Number of components in a scan, 3 - 0x01, // Component Id Y - 0x00, // DC/AC Huffman table - 0x02, // Component Id Cb - 0x11, // DC/AC Huffman table - 0x03, // Component Id Cr - 0x11, // DC/AC Huffman table - 0x00, // Ss - Start of spectral selection. - 0x3f, // Se - End of spectral selection. - 0x00 // Ah + Ah (Successive approximation bit position high + low) - }; + private Block8x8F chrominanceQuantTable; /// /// The accumulated bits to write to the stream. @@ -249,15 +256,13 @@ namespace ImageSharp.Formats /// /// The AC luminance huffman table index /// - LuminanceAC = 1, - /// /// The DC chrominance huffman table index /// ChrominanceDC = 2, - + /// /// The AC chrominance huffman table index /// @@ -282,26 +287,6 @@ namespace ImageSharp.Formats Chrominance = 1, } - private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) - { - for (int j = 0; j < Block8x8F.ScalarCount; j++) - { - int x = UnscaledQuant[i, j]; - x = ((x * scale) + 50) / 100; - if (x < 1) - { - x = 1; - } - - if (x > 255) - { - x = 255; - } - - quant[j] = x; - } - } - /// /// Encode writes the image to the jpeg baseline format with the given options. /// @@ -325,11 +310,6 @@ namespace ImageSharp.Formats this.outputStream = stream; this.subsample = sample; - //for (int i = 0; i < QuantizationTableCount; i++) - //{ - // this.quant[i] = new float[]; - //} - if (quality < 1) { quality = 1; @@ -352,10 +332,9 @@ namespace ImageSharp.Formats } // Initialize the quantization tables. - InitQuantizationTable(0, scale, ref this.luminanceQuantTable); InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); - + // Compute number of components based on input image type. int componentCount = 3; @@ -385,7 +364,80 @@ namespace ImageSharp.Formats stream.Write(this.buffer, 0, 2); stream.Flush(); } - + + private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) + { + for (int j = 0; j < Block8x8F.ScalarCount; j++) + { + int x = UnscaledQuant[i, j]; + x = ((x * scale) + 50) / 100; + if (x < 1) + { + x = 1; + } + + if (x > 255) + { + x = 255; + } + + quant[j] = x; + } + } + + /// + /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values. + /// + /// The pixel format. + /// The pixel accessor. + /// The x-position within the image. + /// The y-position within the image. + /// The luminance block. + /// The red chroma block. + /// The blue chroma block. + private static void ToYCbCr( + PixelAccessor pixels, + int x, + int y, + Block8x8F* yBlock, + Block8x8F* cbBlock, + Block8x8F* crBlock) + where TColor : struct, IPackedPixel, IEquatable + { + float* yBlockRaw = (float*)yBlock; + float* cbBlockRaw = (float*)cbBlock; + float* crBlockRaw = (float*)crBlock; + + using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ)) + { + pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x); + + byte* data = (byte*)rgbBytes.DataPointer; + + for (int j = 0; j < 8; j++) + { + int j8 = j * 8; + for (int i = 0; i < 8; i++) + { + Vector3 v = new Vector3(data[0], data[1], data[2]); + + // Convert returned bytes into the YCbCr color space. Assume RGBA + float yy = (0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z); + float cb = 128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z)); + float cr = 128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z)); + + int index = j8 + i; + + yBlockRaw[index] = yy; + cbBlockRaw[index] = cb; + crBlockRaw[index] = cr; + + data += 3; + } + } + } + } + /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits < 1<<nBits && nBits <= 16. @@ -485,30 +537,32 @@ namespace ImageSharp.Formats /// Quantization table /// The 8x8 Unzig block ptr /// The - private float WriteBlock(QuantIndex index, float prevDC, Block8x8F* src, Block8x8F* tempDest, Block8x8F* temp2, Block8x8F* quant, int* unzigPtr) + private float WriteBlock( + QuantIndex index, + float prevDC, + Block8x8F* src, + Block8x8F* tempDest, + Block8x8F* temp2, + Block8x8F* quant, + int* unzigPtr) { DCT.TransformFDCT(ref *src, ref *tempDest, ref *temp2); - + Block8x8F.UnZigDivRound(tempDest, temp2, quant, unzigPtr); - //Block8x8F.RoundAll(tempDest); - + float* d = (float*)temp2; - float* q = (float*)quant; // Emit the DC delta. - //float dc = Round(d[0], q[0]); float dc = d[0]; - this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, (int)(dc - prevDC)); - + // Emit the AC components. HuffIndex h = (HuffIndex)((2 * (int)index) + 1); int runLength = 0; - + for (int zig = 1; zig < Block.BlockSize; zig++) { - //float ac = Round(d[unzigPtr[zig]], q[zig]); float ac = d[zig]; if (ac == 0) @@ -536,107 +590,6 @@ namespace ImageSharp.Formats return dc; } - /// - /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values. - /// - /// The pixel format. - /// The pixel accessor. - /// The x-position within the image. - /// The y-position within the image. - /// The luminance block. - /// The red chroma block. - /// The blue chroma block. - private static void ToYCbCr( - PixelAccessor pixels, - int x, - int y, - Block8x8F* yBlock, - Block8x8F* cbBlock, - Block8x8F* crBlock) - where TColor : struct, IPackedPixel, IEquatable - { - float* yBlockRaw = (float*)yBlock; - float* cbBlockRaw = (float*)cbBlock; - float* crBlockRaw = (float*)crBlock; - - PixelAccessor asStandardColorAccessor = pixels as PixelAccessor; - if (asStandardColorAccessor != null) - { - ColorRGBToYCbCr(asStandardColorAccessor, x, y, yBlockRaw, cbBlockRaw, crBlockRaw); - return; - } - - Vector4 maxBytes = new Vector4(255f); - Vector4 half = new Vector4(0.5f); - int xmax = pixels.Width - 1; - int ymax = pixels.Height - 1; - - for (int j = 0; j < 8; j++) - { - int j8 = j * 8; - for (int i = 0; i < 8; i++) - { - Vector4 v = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToVector4(); - v = v * maxBytes + half; - - // Convert returned bytes into the YCbCr color space. Assume RGBA - float yy = ((0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z)); - float cb = (128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z))); - float cr = (128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z))); - - int index = j8 + i; - - yBlockRaw[index] = yy; - cbBlockRaw[index] = cb; - crBlockRaw[index] = cr; - } - } - } - - // ReSharper disable once InconsistentNaming - private static void ColorRGBToYCbCr( - PixelAccessor pixels, - int x, - int y, - float* yBlockRaw, - float* cbBlockRaw, - float* crBlockRaw) - { - int colorSize = sizeof(Color); - - int xmax = pixels.Width - 1; - int ymax = pixels.Height - 1; - - byte* data = (byte*)pixels.DataPointer; - - for (int j = 0; j < 8; j++) - { - int yPos = Math.Min(y + j, ymax); - - int j8 = j * 8; - for (int i = 0; i < 8; i++) - { - int xPos = Math.Min(x + i, xmax); - - byte* dataPos = data + (((yPos * pixels.Width) + xPos) * colorSize); - Vector3 v = new Vector3(dataPos[0], dataPos[1], dataPos[2]); - - // Convert returned bytes into the YCbCr color space. Assume RGBA - float yy = ((0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z)); - float cb = (128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z))); - float cr = (128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z))); - - int index = j8 + i; - - yBlockRaw[index] = yy; - cbBlockRaw[index] = cb; - crBlockRaw[index] = cr; - } - } - } - - - /// /// Writes the application header containing the JFIF identifier plus extra data. /// @@ -736,10 +689,11 @@ namespace ImageSharp.Formats WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); - + this.outputStream.Write(dqt, 0, dqt.Length); } +#pragma warning disable SA1204 private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) { dqt[offset++] = (byte)i; @@ -748,6 +702,7 @@ namespace ImageSharp.Formats dqt[offset++] = (byte)q[j]; } } +#pragma warning restore SA1204 /// /// Writes the Start Of Frame (Baseline) marker @@ -779,7 +734,9 @@ namespace ImageSharp.Formats this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported this.buffer[3] = (byte)(width >> 8); this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) + this.buffer[5] = (byte)componentCount; + + // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) if (componentCount == 1) { this.buffer[6] = 1; @@ -863,7 +820,7 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel, IEquatable { // TODO: We should allow grayscale writing. - this.outputStream.Write(this.sosHeaderYCbCr, 0, this.sosHeaderYCbCr.Length); + this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); switch (this.subsample) { @@ -887,12 +844,12 @@ namespace ImageSharp.Formats private void Encode444(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - Block8x8F b = new Block8x8F(); - Block8x8F cb = new Block8x8F(); - Block8x8F cr = new Block8x8F(); + Block8x8F b = default(Block8x8F); + Block8x8F cb = default(Block8x8F); + Block8x8F cr = default(Block8x8F); - Block8x8F temp1 = new Block8x8F(); - Block8x8F temp2 = new Block8x8F(); + Block8x8F temp1 = default(Block8x8F); + Block8x8F temp2 = default(Block8x8F); Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; @@ -908,16 +865,42 @@ namespace ImageSharp.Formats { ToYCbCr(pixels, x, y, &b, &cb, &cr); - prevDCY = this.WriteBlock(QuantIndex.Luminance, prevDCY, &b, &temp1, &temp2, &onStackLuminanceQuantTable, unzig.Data); - prevDCCb = this.WriteBlock(QuantIndex.Chrominance, prevDCCb, &cb, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data); - prevDCCr = this.WriteBlock(QuantIndex.Chrominance, prevDCCr, &cr, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data); + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &b, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &cb, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &cr, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); } } } - struct BlockQuad +#pragma warning disable SA1201 // MethodShouldNotFollowAStruct + + /// + /// This struct belongs to Encode420. Much easeier to understand code if they are together. Why should I move it Up? :P + /// + private struct BlockQuad { - public fixed float Data[4*Block8x8F.ScalarCount]; + public fixed float Data[4 * Block8x8F.ScalarCount]; } /// @@ -929,15 +912,15 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - Block8x8F b = new Block8x8F(); - - BlockQuad cb = new BlockQuad(); - BlockQuad cr = new BlockQuad(); + Block8x8F b = default(Block8x8F); + + BlockQuad cb = default(BlockQuad); + BlockQuad cr = default(BlockQuad); Block8x8F* cbPtr = (Block8x8F*)cb.Data; Block8x8F* crPtr = (Block8x8F*)cr.Data; - - Block8x8F temp1 = new Block8x8F(); - Block8x8F temp2 = new Block8x8F(); + + Block8x8F temp1 = default(Block8x8F); + Block8x8F temp2 = default(Block8x8F); Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; @@ -990,6 +973,8 @@ namespace ImageSharp.Formats } } +#pragma warning restore SA1201 + /// /// Writes the header for a marker with the given length. /// @@ -1040,11 +1025,6 @@ namespace ImageSharp.Formats /// private class HuffmanLut { - /// - /// The collection of huffman values. - /// - public uint[] Values { get; } - /// /// Initializes a new instance of the class. /// @@ -1079,6 +1059,11 @@ namespace ImageSharp.Formats code <<= 1; } } + + /// + /// Gets the collection of huffman values. + /// + public uint[] Values { get; } } } } diff --git a/src/ImageSharp/Formats/Jpg/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/JpegUtils.cs index f881a4088..67b33ea25 100644 --- a/src/ImageSharp/Formats/Jpg/JpegUtils.cs +++ b/src/ImageSharp/Formats/Jpg/JpegUtils.cs @@ -1,10 +1,43 @@ -namespace ImageSharp.Formats +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats { using System; using System.Runtime.CompilerServices; + /// + /// Jpeg specific utilities and extension methods + /// internal static unsafe class JpegUtils { + /// + /// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of the image. + /// + /// The pixel type + /// The input pixel acessor + /// The destination + /// Starting Y coord + /// Starting X coord + public static void CopyRGBBytesStretchedTo( + this PixelAccessor pixels, + PixelArea dest, + int sourceY, + int sourceX) + where TColor : struct, IPackedPixel, IEquatable + { + pixels.CopyTo(dest, sourceY, sourceX); + int stretchFromX = pixels.Width - sourceX; + int stretchFromY = pixels.Height - sourceY; + StretchPixels(dest, stretchFromX, stretchFromY); + } + + /// + /// Copy an RGB value + /// + /// Source pointer + /// Destination pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void CopyRgb(byte* source, byte* dest) { @@ -13,7 +46,7 @@ *dest = *source; // B } - private static unsafe void StretchPixels(PixelArea area, int fromX, int fromY) + private static void StretchPixels(PixelArea area, int fromX, int fromY) where TColor : struct, IPackedPixel, IEquatable { if (IsInvalidStretchArea(area, fromX, fromY)) @@ -23,12 +56,12 @@ for (int y = 0; y < fromY; y++) { - byte* ptrBase = (byte*)area.DataPointer + y * area.RowByteCount; + byte* ptrBase = (byte*)area.DataPointer + (y * area.RowByteCount); for (int x = fromX; x < area.Width; x++) { - byte* prevPtr = ptrBase + (x - 1) * 3; - byte* currPtr = ptrBase + x * 3; + byte* prevPtr = ptrBase + ((x - 1) * 3); + byte* currPtr = ptrBase + (x * 3); CopyRgb(prevPtr, currPtr); } @@ -36,8 +69,8 @@ for (int y = fromY; y < area.Height; y++) { - byte* currBase = (byte*)area.DataPointer + y * area.RowByteCount; - byte* prevBase = (byte*)area.DataPointer + (y - 1) * area.RowByteCount; + byte* currBase = (byte*)area.DataPointer + (y * area.RowByteCount); + byte* prevBase = (byte*)area.DataPointer + ((y - 1) * area.RowByteCount); for (int x = 0; x < area.Width; x++) { @@ -51,22 +84,10 @@ } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsInvalidStretchArea(PixelArea area, int fromX, int fromY) where TColor : struct, IPackedPixel, IEquatable - { - return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; - } - - public static void CopyRGBBytesStretchedTo( - this PixelAccessor pixels, - PixelArea dest, - int sourceY, - int sourceX) + private static bool IsInvalidStretchArea(PixelArea area, int fromX, int fromY) where TColor : struct, IPackedPixel, IEquatable { - pixels.CopyTo(dest, sourceY, sourceX); - int stretchFromX = pixels.Width - sourceX; - int stretchFromY = pixels.Height - sourceY; - StretchPixels(dest, stretchFromX, stretchFromY); + return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/UnzigData.cs b/src/ImageSharp/Formats/Jpg/UnzigData.cs index 6dae6d942..e74dd5c73 100644 --- a/src/ImageSharp/Formats/Jpg/UnzigData.cs +++ b/src/ImageSharp/Formats/Jpg/UnzigData.cs @@ -1,4 +1,8 @@ -namespace ImageSharp.Formats +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats { using System; using System.Runtime.InteropServices; @@ -11,15 +15,10 @@ /// internal unsafe struct UnzigData { - internal fixed int Data[64]; - - public static UnzigData Create() - { - UnzigData result = new UnzigData(); - int* unzigPtr = result.Data; - Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); - return result; - } + /// + /// Copy of in a value type + /// + public fixed int Data[64]; /// /// Unzig maps from the zigzag ordering to the natural ordering. For example, @@ -34,5 +33,16 @@ 53, 60, 61, 54, 47, 55, 62, 63, }; + /// + /// Creates and fills an instance of with Jpeg unzig indices + /// + /// The new instance + public static UnzigData Create() + { + UnzigData result = default(UnzigData); + int* unzigPtr = result.Data; + Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); + return result; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 3642d3942..1dd3edf94 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -186,8 +186,7 @@ namespace ImageSharp int width = Math.Min(area.Width, this.Width - targetX); int height = Math.Min(area.Height, this.Height - targetY); - this.CheckDimensions(width, height); - + // this.CheckDimensions(width, height); TODO: Why was width == 0 or height == 0 considered a problem? Copy implementations do not fail on this (just do nothing)! switch (area.ComponentOrder) { case ComponentOrder.ZYX: @@ -221,8 +220,7 @@ namespace ImageSharp int width = Math.Min(area.Width, this.Width - sourceX); int height = Math.Min(area.Height, this.Height - sourceY); - this.CheckDimensions(width, height); - + // this.CheckDimensions(width, height); TODO: Why was width == 0 or height == 0 considered a problem? Copy implementations do not fail on this (just do nothing)! switch (area.ComponentOrder) { case ComponentOrder.ZYX: diff --git a/src/ImageSharp/PixelAccessor.cs b/src/ImageSharp/PixelAccessor.cs index 378ec55dc..c91f10cbd 100644 --- a/src/ImageSharp/PixelAccessor.cs +++ b/src/ImageSharp/PixelAccessor.cs @@ -109,6 +109,26 @@ namespace ImageSharp } } + /// + protected override unsafe void CopyToXYZ(PixelArea area, int sourceY, int sourceX, int width, int height) + { + for (int y = 0; y < height; y++) + { + byte* source = this.GetRowPointer(sourceX, sourceY + y); + byte* destination = area.PixelBase + (y * area.RowByteCount); + + for (int x = 0; x < width; x++) + { + *destination = *(source + 0); + *(destination + 1) = *(source + 1); + *(destination + 2) = *(source + 2); + + source += 4; + destination += 3; + } + } + } + /// protected override void CopyToZYXW(PixelArea area, int sourceY, int sourceX, int width, int height) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index fb1ca9371..13d31ce51 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -57,6 +57,34 @@ namespace ImageSharp.Tests } } + private const int BenchmarkExecTimes = 2; + + [Theory( + //Skip = "Benchmark, enable manually!" + )] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio420, 75)] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio444, 75)] + public void Benchmark_JpegEncoder(TestImageProvider provider, JpegSubsample subSample, int quality) + where TColor : struct, IPackedPixel, IEquatable + { + var image = provider.GetImage(); + + using (var outputStream = new MemoryStream()) + { + var encoder = new JpegEncoder() + { + Subsample = subSample, + Quality = quality + }; + + for (int i = 0; i < BenchmarkExecTimes; i++) + { + image.Save(outputStream, encoder); + outputStream.Seek(0, SeekOrigin.Begin); + } + } + } + public static Image CreateTestImage(GenericFactory factory) where TColor : struct, IPackedPixel, IEquatable { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index be5559e2a..911719afa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -90,5 +90,11 @@ namespace ImageSharp.Tests return this; } + + public override string ToString() + { + string provName = this.GetType().Name.Replace("Provider", ""); + return $"{this.SourceFileOrDescription}[{this.PixelType}]"; + } } } \ No newline at end of file From 661e32f143d3e05bc0e7550155e7d60d213cea57 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 07:01:25 +0100 Subject: [PATCH 07/15] pooling PixelArea.Bytes --- src/ImageSharp/Image/PixelArea{TColor}.cs | 115 +++++++++++------- .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 11 +- 2 files changed, 79 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 673fe5500..f8e46a6cc 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -2,10 +2,10 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp { using System; + using System.Buffers; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; @@ -39,6 +39,11 @@ namespace ImageSharp /// private bool isDisposed; + /// + /// True if was allocated by the constructor (rented from ) + /// + private bool isBufferOwner; + /// /// Initializes a new instance of the class. /// @@ -96,7 +101,7 @@ namespace ImageSharp /// The width. /// The component order. public PixelArea(int width, ComponentOrder componentOrder) - : this(width, 1, componentOrder, 0) + : this(width, 1, componentOrder, 0) { } @@ -107,7 +112,7 @@ namespace ImageSharp /// The component order. /// The number of bytes to pad each row. public PixelArea(int width, ComponentOrder componentOrder, int padding) - : this(width, 1, componentOrder, padding) + : this(width, 1, componentOrder, padding) { } @@ -124,7 +129,8 @@ namespace ImageSharp this.Height = height; this.ComponentOrder = componentOrder; this.RowByteCount = (width * GetComponentCount(componentOrder)) + padding; - this.Bytes = new byte[this.RowByteCount * height]; + this.Bytes = BytesPool.Rent(this.RowByteCount * height); + this.isBufferOwner = true; this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); // TODO: Why is Resharper warning us about an impure method call? @@ -137,7 +143,7 @@ namespace ImageSharp /// ~PixelArea() { - this.Dispose(); + this.Dispose(false); } /// @@ -145,20 +151,30 @@ namespace ImageSharp /// public byte[] Bytes { get; } + /// + /// Gets the component order. + /// + public ComponentOrder ComponentOrder { get; } + /// /// Gets the pointer to the pixel buffer. /// public IntPtr DataPointer => this.dataPointer; + /// + /// Gets the height. + /// + public int Height { get; } + /// /// Gets the data pointer. /// public byte* PixelBase { get; private set; } /// - /// Gets the component order. + /// Gets number of bytes in a row. /// - public ComponentOrder ComponentOrder { get; } + public int RowByteCount { get; } /// /// Gets the width. @@ -166,14 +182,17 @@ namespace ImageSharp public int Width { get; } /// - /// Gets the height. + /// Gets the pool used to rent , when it's not coming from an external source /// - public int Height { get; } + private static ArrayPool BytesPool => ArrayPool.Shared; /// - /// Gets number of bytes in a row. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - public int RowByteCount { get; } + public void Dispose() + { + this.Dispose(true); + } /// /// Reads the stream to the area. @@ -193,39 +212,6 @@ namespace ImageSharp stream.Write(this.Bytes, 0, this.Bytes.Length); } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - if (this.PixelBase == null) - { - return; - } - - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - this.dataPointer = IntPtr.Zero; - this.PixelBase = null; - - this.isDisposed = true; - - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); - } - /// /// Resets the bytes of the array to it's initial value. /// @@ -265,8 +251,45 @@ namespace ImageSharp int requiredLength = (width * GetComponentCount(componentOrder)) * height; if (bytes.Length != requiredLength) { - throw new ArgumentOutOfRangeException(nameof(bytes), $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); + throw new ArgumentOutOfRangeException( + nameof(bytes), + $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); } } + + private void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (this.PixelBase == null) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + if (disposing && this.isBufferOwner) + { + BytesPool.Return(this.Bytes); + } + + this.dataPointer = IntPtr.Zero; + this.PixelBase = null; + + this.isDisposed = true; + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 13d31ce51..35b1ff0d9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -19,6 +19,7 @@ namespace ImageSharp.Tests { this.Output = output; } + public static IEnumerable AllJpegFiles => TestImages.Jpeg.All; [Theory] @@ -57,7 +58,15 @@ namespace ImageSharp.Tests } } - private const int BenchmarkExecTimes = 2; + private const int BenchmarkExecTimes = 30; + + public static readonly string[] BenchmarkFiles = + { + TestImages.Bmp.Car, TestImages.Bmp.NegHeight, + TestImages.Bmp.F, TestImages.Png.Splash, + TestImages.Jpeg.Jpeg420, TestImages.Jpeg.Calliphora, + TestImages.Jpeg.Cmyk + }; [Theory( //Skip = "Benchmark, enable manually!" From 8ad68ccbb9d806f062208f5eab97536564dad83d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 07:17:50 +0100 Subject: [PATCH 08/15] fixed TestImageProvider.FileProvider bug --- ImageSharp.sln.DotSettings | 1 + tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 7 +++++-- .../TestUtilities/Factories/GenericFactory.cs | 5 +++++ .../TestUtilities/Factories/ImageFactory.cs | 6 ++++++ .../TestUtilities/ImageProviders/FileProvider.cs | 2 +- .../TestUtilities/Tests/TestImageProviderTests.cs | 1 + 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index fb607752a..5acec071a 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -349,6 +349,7 @@ RGB RLE XY + XYZ $object$_On$event$ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 35b1ff0d9..48d5454f6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -68,11 +68,14 @@ namespace ImageSharp.Tests TestImages.Jpeg.Cmyk }; + private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; + //PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb; + [Theory( //Skip = "Benchmark, enable manually!" )] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio420, 75)] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio444, 75)] + [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)] + [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio444, 75)] public void Benchmark_JpegEncoder(TestImageProvider provider, JpegSubsample subSample, int quality) where TColor : struct, IPackedPixel, IEquatable { diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs index 8e164cb3c..c2f0aed84 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs @@ -23,5 +23,10 @@ namespace ImageSharp.Tests { return new Image(bytes); } + + public virtual Image CreateImage(Image other) + { + return new Image(other); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs index 288dbdd8c..a8d398c1e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs @@ -10,5 +10,11 @@ namespace ImageSharp.Tests public override Image CreateImage(byte[] bytes) => new Image(bytes); public override Image CreateImage(int width, int height) => new Image(width, height); + + public override Image CreateImage(Image other) + { + Image img = (Image)other; + return new Image(img); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index a2848ff6f..1b8c1498e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -47,7 +47,7 @@ namespace ImageSharp.Tests return this.Factory.CreateImage(testFile.Bytes); }); - return new Image(cachedImage); + return this.Factory.CreateImage(cachedImage); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 28e5ad2c2..31014f89d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -57,6 +57,7 @@ namespace ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.StandardImageClass)] + [WithFile(TestImages.Bmp.F, PixelTypes.StandardImageClass)] public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass( TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable From 2999ef15733676f773edbe747fbc106cf92185d8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 24 Dec 2016 08:06:09 +0100 Subject: [PATCH 09/15] fixed PixelArea pooling + added reusing of PixelArea-s in JpegEncoderCore --- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 166 +++++++++--------- src/ImageSharp/Image/PixelArea{TColor}.cs | 39 ++-- 2 files changed, 114 insertions(+), 91 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 4444e50bf..d6c387911 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -395,45 +395,45 @@ namespace ImageSharp.Formats /// The luminance block. /// The red chroma block. /// The blue chroma block. + /// Temporal provided by the caller private static void ToYCbCr( PixelAccessor pixels, int x, int y, Block8x8F* yBlock, Block8x8F* cbBlock, - Block8x8F* crBlock) + Block8x8F* crBlock, + PixelArea rgbBytes) where TColor : struct, IPackedPixel, IEquatable { float* yBlockRaw = (float*)yBlock; float* cbBlockRaw = (float*)cbBlock; float* crBlockRaw = (float*)crBlock; - using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ)) - { - pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x); + rgbBytes.Reset(); + pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x); - byte* data = (byte*)rgbBytes.DataPointer; + byte* data = (byte*)rgbBytes.DataPointer; - for (int j = 0; j < 8; j++) + for (int j = 0; j < 8; j++) + { + int j8 = j * 8; + for (int i = 0; i < 8; i++) { - int j8 = j * 8; - for (int i = 0; i < 8; i++) - { - Vector3 v = new Vector3(data[0], data[1], data[2]); + Vector3 v = new Vector3(data[0], data[1], data[2]); - // Convert returned bytes into the YCbCr color space. Assume RGBA - float yy = (0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z); - float cb = 128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z)); - float cr = 128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z)); + // Convert returned bytes into the YCbCr color space. Assume RGBA + float yy = (0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z); + float cb = 128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z)); + float cr = 128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z)); - int index = j8 + i; + int index = j8 + i; - yBlockRaw[index] = yy; - cbBlockRaw[index] = cb; - crBlockRaw[index] = cr; + yBlockRaw[index] = yy; + cbBlockRaw[index] = cb; + crBlockRaw[index] = cr; - data += 3; - } + data += 3; } } } @@ -844,6 +844,7 @@ namespace ImageSharp.Formats private void Encode444(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { + // TODO: Need a JpegEncoderCoreCore class or struct to hold all this mess: Block8x8F b = default(Block8x8F); Block8x8F cb = default(Block8x8F); Block8x8F cr = default(Block8x8F); @@ -859,36 +860,39 @@ namespace ImageSharp.Formats // ReSharper disable once InconsistentNaming float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - for (int y = 0; y < pixels.Height; y += 8) + using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true)) { - for (int x = 0; x < pixels.Width; x += 8) + for (int y = 0; y < pixels.Height; y += 8) { - ToYCbCr(pixels, x, y, &b, &cb, &cr); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - &b, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &cb, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &cr, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); + for (int x = 0; x < pixels.Width; x += 8) + { + ToYCbCr(pixels, x, y, &b, &cb, &cr, rgbBytes); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &b, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &cb, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &cr, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + } } } } @@ -912,6 +916,7 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { + // TODO: Need a JpegEncoderCoreCore class or struct to hold all this mess: Block8x8F b = default(Block8x8F); BlockQuad cb = default(BlockQuad); @@ -930,45 +935,48 @@ namespace ImageSharp.Formats // ReSharper disable once InconsistentNaming float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - for (int y = 0; y < pixels.Height; y += 16) + using (var rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true)) { - for (int x = 0; x < pixels.Width; x += 16) + for (int y = 0; y < pixels.Height; y += 16) { - for (int i = 0; i < 4; i++) + for (int x = 0; x < pixels.Width; x += 16) { - int xOff = (i & 1) * 8; - int yOff = (i & 2) * 4; - - ToYCbCr(pixels, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, + for (int i = 0; i < 4; i++) + { + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + ToYCbCr(pixels, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i, rgbBytes); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &b, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + } + + Block8x8F.Scale16X16To8X8(&b, cbPtr); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, &b, &temp1, &temp2, - &onStackLuminanceQuantTable, + &onStackChrominanceQuantTable, + unzig.Data); + Block8x8F.Scale16X16To8X8(&b, crPtr); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, unzig.Data); } - - Block8x8F.Scale16X16To8X8(&b, cbPtr); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - Block8x8F.Scale16X16To8X8(&b, crPtr); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); } } } diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index f8e46a6cc..51e788d19 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -28,6 +28,11 @@ namespace ImageSharp /// private IntPtr dataPointer; + /// + /// True if was rented from by the constructor + /// + private bool isBufferRented; + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -39,11 +44,6 @@ namespace ImageSharp /// private bool isDisposed; - /// - /// True if was allocated by the constructor (rented from ) - /// - private bool isBufferOwner; - /// /// Initializes a new instance of the class. /// @@ -90,8 +90,9 @@ namespace ImageSharp /// The width. /// The height. /// The component order. - public PixelArea(int width, int height, ComponentOrder componentOrder) - : this(width, height, componentOrder, 0) + /// True if the buffer should be rented from ArrayPool + public PixelArea(int width, int height, ComponentOrder componentOrder, bool usePool = false) + : this(width, height, componentOrder, 0, usePool) { } @@ -123,14 +124,27 @@ namespace ImageSharp /// The height. /// The component order. /// The number of bytes to pad each row. - public PixelArea(int width, int height, ComponentOrder componentOrder, int padding) + /// True if the buffer should be rented from ArrayPool + public PixelArea(int width, int height, ComponentOrder componentOrder, int padding, bool usePool = false) { this.Width = width; this.Height = height; this.ComponentOrder = componentOrder; this.RowByteCount = (width * GetComponentCount(componentOrder)) + padding; - this.Bytes = BytesPool.Rent(this.RowByteCount * height); - this.isBufferOwner = true; + + var bufferSize = this.RowByteCount * height; + + if (usePool) + { + this.Bytes = BytesPool.Rent(bufferSize); + this.isBufferRented = true; + Array.Clear(this.Bytes, 0, bufferSize); + } + else + { + this.Bytes = new byte[bufferSize]; + } + this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); // TODO: Why is Resharper warning us about an impure method call? @@ -184,6 +198,7 @@ namespace ImageSharp /// /// Gets the pool used to rent , when it's not coming from an external source /// + // ReSharper disable once StaticMemberInGenericType private static ArrayPool BytesPool => ArrayPool.Shared; /// @@ -217,7 +232,7 @@ namespace ImageSharp /// internal void Reset() { - Unsafe.InitBlock(this.PixelBase, 0, (uint)this.Bytes.Length); + Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowByteCount * this.Height)); } /// @@ -274,7 +289,7 @@ namespace ImageSharp this.pixelsHandle.Free(); } - if (disposing && this.isBufferOwner) + if (disposing && this.isBufferRented) { BytesPool.Return(this.Bytes); } From f4477329da1057b740975fea83beae43e9695948 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sat, 24 Dec 2016 08:10:54 +0100 Subject: [PATCH 10/15] more usePool constructors --- src/ImageSharp/Image/PixelArea{TColor}.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 51e788d19..40e9e4e88 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -101,8 +101,9 @@ namespace ImageSharp /// /// The width. /// The component order. - public PixelArea(int width, ComponentOrder componentOrder) - : this(width, 1, componentOrder, 0) + /// True if the buffer should be rented from ArrayPool + public PixelArea(int width, ComponentOrder componentOrder, bool usePool = false) + : this(width, 1, componentOrder, 0, usePool) { } @@ -112,8 +113,9 @@ namespace ImageSharp /// The width. /// The component order. /// The number of bytes to pad each row. - public PixelArea(int width, ComponentOrder componentOrder, int padding) - : this(width, 1, componentOrder, padding) + /// True if the buffer should be rented from ArrayPool + public PixelArea(int width, ComponentOrder componentOrder, int padding, bool usePool = false) + : this(width, 1, componentOrder, padding, usePool) { } From 762f1878a8e713b4db386171fbac51ed9dcb6596 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sat, 24 Dec 2016 08:43:41 +0100 Subject: [PATCH 11/15] removed old Block + related classes, added EncodeJpegMultiple & MultiImageBenchmarkBase --- .../Formats/Jpg/Components/Block.cs | 272 ------------------ .../Formats/Jpg/Components/Block8x8F.cs | 20 +- src/ImageSharp/Formats/Jpg/Components/FDCT.cs | 162 ----------- src/ImageSharp/Formats/Jpg/Components/IDCT.cs | 169 ----------- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 24 +- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 6 +- .../Image/DecodeJpegMultiple.cs | 96 +------ .../Image/EncodeJpegMultiple.cs | 50 ++++ .../Image/MultiImageBenchmarkBase.cs | 185 ++++++++++++ .../Jpg/ReferenceImplementationsTests.cs | 21 +- 10 files changed, 266 insertions(+), 739 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpg/Components/Block.cs delete mode 100644 src/ImageSharp/Formats/Jpg/Components/FDCT.cs delete mode 100644 src/ImageSharp/Formats/Jpg/Components/IDCT.cs create mode 100644 tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs create mode 100644 tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs diff --git a/src/ImageSharp/Formats/Jpg/Components/Block.cs b/src/ImageSharp/Formats/Jpg/Components/Block.cs deleted file mode 100644 index 0531153cd..000000000 --- a/src/ImageSharp/Formats/Jpg/Components/Block.cs +++ /dev/null @@ -1,272 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.Buffers; - using System.Runtime.CompilerServices; - - /// - /// Represents an 8x8 block of coefficients to transform and encode. - /// - internal struct Block : IDisposable - { - /// - /// Gets the size of the block. - /// - public const int BlockSize = 64; - - /// - /// Gets the array of block data. - /// - public int[] Data; - - /// - /// A pool of reusable buffers. - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(BlockSize, 50); - - /// - /// Gets a value indicating whether the block is initialized - /// - public bool IsInitialized => this.Data != null; - - /// - /// Gets the pixel data at the given block index. - /// - /// The index of the data to return. - /// - /// The . - /// - public int this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return this.Data[index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Data[index] = value; - } - } - - /// - /// Creates a new block - /// - /// The - public static Block Create() - { - Block block = default(Block); - block.Init(); - return block; - } - - /// - /// Returns an array of blocks of the given length. - /// - /// The number to create. - /// The - public static Block[] CreateArray(int count) - { - Block[] result = new Block[count]; - for (int i = 0; i < result.Length; i++) - { - result[i].Init(); - } - - return result; - } - - /// - /// Disposes of the collection of blocks - /// - /// The blocks. - public static void DisposeAll(Block[] blocks) - { - for (int i = 0; i < blocks.Length; i++) - { - blocks[i].Dispose(); - } - } - - /// - /// Initializes the new block. - /// - public void Init() - { - this.Data = ArrayPool.Rent(BlockSize); - } - - /// - public void Dispose() - { - // TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement! - if (this.Data != null) - { - ArrayPool.Return(this.Data, true); - this.Data = null; - } - } - - /// - /// Clears the block data - /// - public void Clear() - { - for (int i = 0; i < this.Data.Length; i++) - { - this.Data[i] = 0; - } - } - - /// - /// Clones the current block - /// - /// The - public Block Clone() - { - Block clone = Create(); - Array.Copy(this.Data, clone.Data, BlockSize); - return clone; - } - } - - /// - /// TODO: Should be removed, when JpegEncoderCore is refactored to use Block8x8F - /// Temporal class to make refactoring easier. - /// 1. Refactor Block -> BlockF - /// 2. Test - /// 3. Refactor BlockF -> Block8x8F - /// - internal struct BlockF : IDisposable - { - /// - /// Size of the block. - /// - public const int BlockSize = 64; - - /// - /// The array of block data. - /// - public float[] Data; - - /// - /// A pool of reusable buffers. - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(BlockSize, 50); - - /// - /// Gets a value indicating whether the block is initialized - /// - public bool IsInitialized => this.Data != null; - - /// - /// Gets the pixel data at the given block index. - /// - /// The index of the data to return. - /// - /// The . - /// - public float this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return this.Data[index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Data[index] = value; - } - } - - /// - /// Creates a new block - /// - /// The - public static BlockF Create() - { - var block = default(BlockF); - block.Init(); - return block; - } - - /// - /// Returns an array of blocks of the given length. - /// - /// The number to create. - /// The - public static BlockF[] CreateArray(int count) - { - BlockF[] result = new BlockF[count]; - for (int i = 0; i < result.Length; i++) - { - result[i].Init(); - } - - return result; - } - - /// - /// Disposes of the collection of blocks - /// - /// The blocks. - public static void DisposeAll(BlockF[] blocks) - { - for (int i = 0; i < blocks.Length; i++) - { - blocks[i].Dispose(); - } - } - - /// - /// Clears the block data - /// - public void Clear() - { - for (int i = 0; i < this.Data.Length; i++) - { - this.Data[i] = 0; - } - } - - /// - /// Clones the current block - /// - /// The - public BlockF Clone() - { - BlockF clone = Create(); - Array.Copy(this.Data, clone.Data, BlockSize); - return clone; - } - - /// - /// Initializes the new block. - /// - public void Init() - { - // this.Data = new int[BlockSize]; - this.Data = ArrayPool.Rent(BlockSize); - } - - /// - public void Dispose() - { - // TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement! - if (this.Data != null) - { - ArrayPool.Return(this.Data, true); - this.Data = null; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 89b838289..66ef146e4 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -229,7 +229,7 @@ namespace ImageSharp.Formats { float* b = (float*)blockPtr; float* qtp = (float*)qtPtr; - for (int zig = 0; zig < BlockF.BlockSize; zig++) + for (int zig = 0; zig < ScalarCount; zig++) { float* unzigPos = b + unzigPtr[zig]; float val = *unzigPos; @@ -280,24 +280,6 @@ namespace ImageSharp.Formats this = default(Block8x8F); } - /// - /// TODO: Should be removed when BlockF goes away - /// - /// Legacy block - public void LoadFrom(ref BlockF legacyBlock) - { - this.LoadFrom(legacyBlock.Data); - } - - /// - /// TODO: Should be removed when BlockF goes away - /// - /// Legacy block - public void CopyTo(ref BlockF legacyBlock) - { - this.CopyTo(legacyBlock.Data); - } - /// /// Level shift by +128, clip to [0, 255], and write to buffer. /// diff --git a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs b/src/ImageSharp/Formats/Jpg/Components/FDCT.cs deleted file mode 100644 index 650656ab2..000000000 --- a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs +++ /dev/null @@ -1,162 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Performs a fast, forward discrete cosine transform against the given block - /// decomposing it into 64 orthogonal basis signals. - /// - internal class FDCT - { - // Trigonometric constants in 13-bit fixed point format. - // TODO: Rename and describe these. -#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore - private const int Fix_0_298631336 = 2446; - private const int Fix_0_390180644 = 3196; - private const int Fix_0_541196100 = 4433; - private const int Fix_0_765366865 = 6270; - private const int Fix_0_899976223 = 7373; - private const int Fix_1_175875602 = 9633; - private const int Fix_1_501321110 = 12299; - private const int Fix_1_847759065 = 15137; - private const int Fix_1_961570560 = 16069; - private const int Fix_2_053119869 = 16819; - private const int Fix_2_562915447 = 20995; - private const int Fix_3_072711026 = 25172; -#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore - - /// - /// The number of bits - /// - private const int Bits = 13; - - /// - /// The number of bits to shift by on the first pass. - /// - private const int Pass1Bits = 2; - - /// - /// The value to shift by - /// - private const int CenterJSample = 128; - - /// - /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. - /// - /// The block of coefficients. - public static void Transform(ref Block block) - { - // Pass 1: process rows. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - int x0 = block[y8]; - int x1 = block[y8 + 1]; - int x2 = block[y8 + 2]; - int x3 = block[y8 + 3]; - int x4 = block[y8 + 4]; - int x5 = block[y8 + 5]; - int x6 = block[y8 + 6]; - int x7 = block[y8 + 7]; - - int tmp0 = x0 + x7; - int tmp1 = x1 + x6; - int tmp2 = x2 + x5; - int tmp3 = x3 + x4; - - int tmp10 = tmp0 + tmp3; - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = x0 - x7; - tmp1 = x1 - x6; - tmp2 = x2 - x5; - tmp3 = x3 - x4; - - block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; - block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * Fix_0_541196100; - z1 += 1 << (Bits - Pass1Bits - 1); - block[y8 + 2] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits - Pass1Bits); - block[y8 + 6] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits - Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * Fix_1_175875602; - z1 += 1 << (Bits - Pass1Bits - 1); - tmp0 = tmp0 * Fix_1_501321110; - tmp1 = tmp1 * Fix_3_072711026; - tmp2 = tmp2 * Fix_2_053119869; - tmp3 = tmp3 * Fix_0_298631336; - tmp10 = tmp10 * -Fix_0_899976223; - tmp11 = tmp11 * -Fix_2_562915447; - tmp12 = tmp12 * -Fix_0_390180644; - tmp13 = tmp13 * -Fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); - block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); - } - - // Pass 2: process columns. - // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. - for (int x = 0; x < 8; x++) - { - int tmp0 = block[x] + block[56 + x]; - int tmp1 = block[8 + x] + block[48 + x]; - int tmp2 = block[16 + x] + block[40 + x]; - int tmp3 = block[24 + x] + block[32 + x]; - - int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = block[x] - block[56 + x]; - tmp1 = block[8 + x] - block[48 + x]; - tmp2 = block[16 + x] - block[40 + x]; - tmp3 = block[24 + x] - block[32 + x]; - - block[x] = (tmp10 + tmp11) >> Pass1Bits; - block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - - int z1 = (tmp12 + tmp13) * Fix_0_541196100; - z1 += 1 << (Bits + Pass1Bits - 1); - block[16 + x] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits + Pass1Bits); - block[48 + x] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits + Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * Fix_1_175875602; - z1 += 1 << (Bits + Pass1Bits - 1); - tmp0 = tmp0 * Fix_1_501321110; - tmp1 = tmp1 * Fix_3_072711026; - tmp2 = tmp2 * Fix_2_053119869; - tmp3 = tmp3 * Fix_0_298631336; - tmp10 = tmp10 * -Fix_0_899976223; - tmp11 = tmp11 * -Fix_2_562915447; - tmp12 = tmp12 * -Fix_0_390180644; - tmp13 = tmp13 * -Fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); - block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); - block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); - block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs b/src/ImageSharp/Formats/Jpg/Components/IDCT.cs deleted file mode 100644 index 8c3a5a238..000000000 --- a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs +++ /dev/null @@ -1,169 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Performs a 2-D Inverse Discrete Cosine Transformation. - /// - internal class IDCT - { - private const int W1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) - private const int W2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) - private const int W3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) - private const int W5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) - private const int W6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) - private const int W7 = 565; // 2048*sqrt(2)*cos(7*pi/16) - - private const int W1pw7 = W1 + W7; - private const int W1mw7 = W1 - W7; - private const int W2pw6 = W2 + W6; - private const int W2mw6 = W2 - W6; - private const int W3pw5 = W3 + W5; - private const int W3mw5 = W3 - W5; - - private const int R2 = 181; // 256/sqrt(2) - - /// - /// Performs a 2-D Inverse Discrete Cosine Transformation. - /// - /// The input coefficients should already have been multiplied by the - /// appropriate quantization table. We use fixed-point computation, with the - /// number of bits for the fractional component varying over the intermediate - /// stages. - /// - /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the - /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on - /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. - /// - /// The source block of coefficients - public static void Transform(ref Block src) - { - // Horizontal 1-D IDCT. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - // If all the AC components are zero, then the IDCT is trivial. - if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && - src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) - { - int dc = src[y8 + 0] << 3; - src[y8 + 0] = dc; - src[y8 + 1] = dc; - src[y8 + 2] = dc; - src[y8 + 3] = dc; - src[y8 + 4] = dc; - src[y8 + 5] = dc; - src[y8 + 6] = dc; - src[y8 + 7] = dc; - continue; - } - - // Prescale. - int x0 = (src[y8 + 0] << 11) + 128; - int x1 = src[y8 + 4] << 11; - int x2 = src[y8 + 6]; - int x3 = src[y8 + 2]; - int x4 = src[y8 + 1]; - int x5 = src[y8 + 7]; - int x6 = src[y8 + 5]; - int x7 = src[y8 + 3]; - - // Stage 1. - int x8 = W7 * (x4 + x5); - x4 = x8 + (W1mw7 * x4); - x5 = x8 - (W1pw7 * x5); - x8 = W3 * (x6 + x7); - x6 = x8 - (W3mw5 * x6); - x7 = x8 - (W3pw5 * x7); - - // Stage 2. - x8 = x0 + x1; - x0 -= x1; - x1 = W6 * (x3 + x2); - x2 = x1 - (W2pw6 * x2); - x3 = x1 + (W2mw6 * x3); - x1 = x4 + x6; - x4 -= x6; - x6 = x5 + x7; - x5 -= x7; - - // Stage 3. - x7 = x8 + x3; - x8 -= x3; - x3 = x0 + x2; - x0 -= x2; - x2 = ((R2 * (x4 + x5)) + 128) >> 8; - x4 = ((R2 * (x4 - x5)) + 128) >> 8; - - // Stage 4. - src[y8 + 0] = (x7 + x1) >> 8; - src[y8 + 1] = (x3 + x2) >> 8; - src[y8 + 2] = (x0 + x4) >> 8; - src[y8 + 3] = (x8 + x6) >> 8; - src[y8 + 4] = (x8 - x6) >> 8; - src[y8 + 5] = (x0 - x4) >> 8; - src[y8 + 6] = (x3 - x2) >> 8; - src[y8 + 7] = (x7 - x1) >> 8; - } - - // Vertical 1-D IDCT. - for (int x = 0; x < 8; x++) - { - // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. - // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so - // we do not bother to check for the all-zero case. - - // Prescale. - int y0 = (src[x] << 8) + 8192; - int y1 = src[32 + x] << 8; - int y2 = src[48 + x]; - int y3 = src[16 + x]; - int y4 = src[8 + x]; - int y5 = src[56 + x]; - int y6 = src[40 + x]; - int y7 = src[24 + x]; - - // Stage 1. - int y8 = (W7 * (y4 + y5)) + 4; - y4 = (y8 + (W1mw7 * y4)) >> 3; - y5 = (y8 - (W1pw7 * y5)) >> 3; - y8 = (W3 * (y6 + y7)) + 4; - y6 = (y8 - (W3mw5 * y6)) >> 3; - y7 = (y8 - (W3pw5 * y7)) >> 3; - - // Stage 2. - y8 = y0 + y1; - y0 -= y1; - y1 = (W6 * (y3 + y2)) + 4; - y2 = (y1 - (W2pw6 * y2)) >> 3; - y3 = (y1 + (W2mw6 * y3)) >> 3; - y1 = y4 + y6; - y4 -= y6; - y6 = y5 + y7; - y5 -= y7; - - // Stage 3. - y7 = y8 + y3; - y8 -= y3; - y3 = y0 + y2; - y0 -= y2; - y2 = ((R2 * (y4 + y5)) + 128) >> 8; - y4 = ((R2 * (y4 - y5)) + 128) >> 8; - - // Stage 4. - src[x] = (y7 + y1) >> 14; - src[8 + x] = (y3 + y2) >> 14; - src[16 + x] = (y0 + y4) >> 14; - src[24 + x] = (y8 + y6) >> 14; - src[32 + x] = (y8 - y6) >> 14; - src[40 + x] = (y0 - y4) >> 14; - src[48 + x] = (y3 - y2) >> 14; - src[56 + x] = (y7 - y1) >> 14; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 960405530..b74ae2317 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -189,7 +189,7 @@ namespace ImageSharp.Formats this.huffmanTrees = new Huffman[(MaxTc + 1) * (MaxTh + 1)]; this.quantizationTables = new Block8x8F[MaxTq + 1]; - this.temp = new byte[2 * BlockF.BlockSize]; + this.temp = new byte[2 * Block8x8F.ScalarCount]; this.componentArray = new Component[MaxComponents]; this.progCoeffs = new Block8x8F[MaxComponents][]; this.bits = default(Bits); @@ -1053,32 +1053,32 @@ namespace ImageSharp.Formats switch (x >> 4) { case 0: - if (remaining < BlockF.BlockSize) + if (remaining < Block8x8F.ScalarCount) { done = true; break; } - remaining -= BlockF.BlockSize; - this.ReadFull(this.temp, 0, BlockF.BlockSize); + remaining -= Block8x8F.ScalarCount; + this.ReadFull(this.temp, 0, Block8x8F.ScalarCount); - for (int i = 0; i < BlockF.BlockSize; i++) + for (int i = 0; i < Block8x8F.ScalarCount; i++) { this.quantizationTables[tq][i] = this.temp[i]; } break; case 1: - if (remaining < 2 * BlockF.BlockSize) + if (remaining < 2 * Block8x8F.ScalarCount) { done = true; break; } - remaining -= 2 * BlockF.BlockSize; - this.ReadFull(this.temp, 0, 2 * BlockF.BlockSize); + remaining -= 2 * Block8x8F.ScalarCount; + this.ReadFull(this.temp, 0, 2 * Block8x8F.ScalarCount); - for (int i = 0; i < BlockF.BlockSize; i++) + for (int i = 0; i < Block8x8F.ScalarCount; i++) { this.quantizationTables[tq][i] = (this.temp[2 * i] << 8) | this.temp[(2 * i) + 1]; } @@ -1473,7 +1473,7 @@ namespace ImageSharp.Formats // significant bit. // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. int zigStart = 0; - int zigEnd = BlockF.BlockSize - 1; + int zigEnd = Block8x8F.ScalarCount - 1; int ah = 0; int al = 0; @@ -1484,7 +1484,7 @@ namespace ImageSharp.Formats ah = this.temp[3 + scanComponentCountX2] >> 4; al = this.temp[3 + scanComponentCountX2] & 0x0f; - if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= BlockF.BlockSize) + if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= Block8x8F.ScalarCount) { throw new ImageFormatException("Bad spectral selection bounds"); } @@ -1788,7 +1788,7 @@ namespace ImageSharp.Formats if (this.isProgressive) { - if (zigEnd != BlockF.BlockSize - 1 || al != 0) + if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0) { // We haven't completely decoded this 8x8 block. Save the coefficients. diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index d6c387911..8c5ed8b90 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -561,7 +561,7 @@ namespace ImageSharp.Formats HuffIndex h = (HuffIndex)((2 * (int)index) + 1); int runLength = 0; - for (int zig = 1; zig < Block.BlockSize; zig++) + for (int zig = 1; zig < Block8x8F.ScalarCount; zig++) { float ac = d[zig]; @@ -679,12 +679,12 @@ namespace ImageSharp.Formats private void WriteDefineQuantizationTables() { // Marker + quantization table lengths - int markerlen = 2 + (QuantizationTableCount * (1 + Block.BlockSize)); + int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount)); this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); // Loop through and collect the tables as one array. // This allows us to reduce the number of writes to the stream. - byte[] dqt = new byte[(QuantizationTableCount * Block.BlockSize) + QuantizationTableCount]; + byte[] dqt = new byte[(QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount]; int offset = 0; WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs index a3c2eba73..68797c778 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs @@ -1,106 +1,38 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// +using System.Collections.Generic; namespace ImageSharp.Benchmarks.Image { - using System; - using System.Collections.Generic; using System.Drawing; using System.IO; - using System.Linq; - using BenchmarkDotNet.Attributes; using Image = ImageSharp.Image; using ImageSharpSize = ImageSharp.Size; - public class DecodeJpegMultiple + public class DecodeJpegMultiple : MultiImageBenchmarkBase { - private const string Folder = "../ImageSharp.Tests/TestImages/Formats/Jpg/"; - - private Dictionary fileNamesToBytes; - - public enum JpegTestingMode + protected override IEnumerable InputImageSubfolders => new[] { - All, - - SmallImagesOnly, - - LargeImagesOnly, + "Formats/Jpg/" + }; - CalliphoraOnly, - } - - [Params(JpegTestingMode.All, JpegTestingMode.SmallImagesOnly, JpegTestingMode.LargeImagesOnly, - JpegTestingMode.CalliphoraOnly)] - public JpegTestingMode Mode { get; set; } - - private IEnumerable> RequestedImages - { - get - { - int thresholdInBytes = 100000; - - switch (this.Mode) - { - case JpegTestingMode.All: - return this.fileNamesToBytes; - case JpegTestingMode.SmallImagesOnly: - return this.fileNamesToBytes.Where(kv => kv.Value.Length < thresholdInBytes); - case JpegTestingMode.LargeImagesOnly: - return this.fileNamesToBytes.Where(kv => kv.Value.Length >= thresholdInBytes); - case JpegTestingMode.CalliphoraOnly: - return new[] { this.fileNamesToBytes.First(kv => kv.Key.ToLower().Contains("calliphora")) }; - default: - throw new ArgumentOutOfRangeException(); - } - } - } + protected override IEnumerable FileFilters => new[] { "*.jpg" }; [Benchmark(Description = "DecodeJpegMultiple - ImageSharp")] - public ImageSharpSize JpegImageSharp() + public void DecodeJpegImageSharp() { - ImageSharpSize lastSize = new ImageSharpSize(); - foreach (var kv in this.RequestedImages) - { - using (MemoryStream memoryStream = new MemoryStream(kv.Value)) - { - Image image = new Image(memoryStream); - lastSize = new ImageSharpSize(image.Width, image.Height); - } - } - - return lastSize; + this.ForEachStream( + ms => new ImageSharp.Image(ms) + ); } [Benchmark(Baseline = true, Description = "DecodeJpegMultiple - System.Drawing")] - public Size JpegSystemDrawing() + public void DecodeJpegSystemDrawing() { - Size lastSize = new Size(); - foreach (var kv in this.RequestedImages) - { - using (MemoryStream memoryStream = new MemoryStream(kv.Value)) - { - using (System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream)) - { - lastSize = image.Size; - } - } - } - - return lastSize; + this.ForEachStream( + System.Drawing.Image.FromStream + ); } - [Setup] - public void ReadImages() - { - if (this.fileNamesToBytes != null) return; - - var allFiles = Directory.EnumerateFiles(Folder, "*.jpg", SearchOption.AllDirectories).ToArray(); - - this.fileNamesToBytes = allFiles.ToDictionary(fn => fn, File.ReadAllBytes); - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs new file mode 100644 index 000000000..d6af8c842 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs @@ -0,0 +1,50 @@ +namespace ImageSharp.Benchmarks.Image +{ + using System; + using System.Collections.Generic; + using System.Drawing.Imaging; + using System.IO; + using System.Linq; + + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Attributes.Jobs; + using BenchmarkDotNet.Engines; + + using ImageSharp.Formats; + + public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded + { + protected override IEnumerable InputImageSubfolders => new[] + { + "Formats/Bmp/", + "Formats/Jpg/baseline" + }; + + protected override IEnumerable FileFilters => new[] { "*.bmp", "*.jpg" }; + + [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] + public void EncodeJpegImageSharp() + { + this.ForEachImageSharpImage( + img => + { + MemoryStream ms = new MemoryStream(); + img.Save(ms, new JpegEncoder()); + return ms; + }); + } + + [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] + public void EncodeJpegSystemDrawing() + { + this.ForEachSystemDrawingImage( + img => + { + MemoryStream ms = new MemoryStream(); + img.Save(ms, ImageFormat.Jpeg); + return ms; + }); + } + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs new file mode 100644 index 000000000..d605bf931 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs @@ -0,0 +1,185 @@ +namespace ImageSharp.Benchmarks.Image +{ + using System; + using System.Collections.Generic; + using System.Drawing; + using System.IO; + using System.Linq; + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + using Image = ImageSharp.Image; + + public abstract class MultiImageBenchmarkBase + { + protected Dictionary FileNamesToBytes = new Dictionary(); + + protected Dictionary FileNamesToImageSharpImages = new Dictionary(); + protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); + + public enum TestingMode + { + All, + + SmallImagesOnly, + + LargeImagesOnly + } + + [Params(TestingMode.All, TestingMode.SmallImagesOnly, TestingMode.LargeImagesOnly)] + public TestingMode Mode { get; set; } + + protected virtual string BaseFolder => "../ImageSharp.Tests/TestImages/"; + + protected abstract IEnumerable FileFilters { get; } + + protected IEnumerable FilterWords => new string[] { }; + + protected virtual IEnumerable Folders => this.InputImageSubfolders.Select(f => Path.Combine(this.BaseFolder, f)); + + protected virtual int LargeImageThresholdInBytes => 100000; + + protected IEnumerable> EnumeratePairsByBenchmarkSettings( + Dictionary input, + Predicate checkIfSmall) + { + switch (this.Mode) + { + case TestingMode.All: + return input; + case TestingMode.SmallImagesOnly: + return input.Where(kv => checkIfSmall(kv.Value)); + case TestingMode.LargeImagesOnly: + return input.Where(kv => !checkIfSmall(kv.Value)); + default: + throw new ArgumentOutOfRangeException(); + } + } + + protected IEnumerable> FileNames2Bytes + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToBytes, + arr => arr.Length < this.LargeImageThresholdInBytes); + + protected abstract IEnumerable InputImageSubfolders { get; } + + [Setup] + public void ReadImages() + { + //Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); + this.ReadImagesImpl(); + } + + protected virtual void ReadImagesImpl() + { + foreach (string folder in this.Folders) + { + var allFiles = + this.FileFilters.SelectMany( + f => + Directory.EnumerateFiles(folder, f, SearchOption.AllDirectories) + .Where(fn => !this.FilterWords.Any(w => fn.ToLower().Contains(w)))).ToArray(); + foreach (var fn in allFiles) + { + this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + } + } + } + + protected void ForEachStream(Func operation) + { + foreach (var kv in this.FileNames2Bytes) + { + using (MemoryStream memoryStream = new MemoryStream(kv.Value)) + { + try + { + var obj = operation(memoryStream); + (obj as IDisposable)?.Dispose(); + + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + } + } + } + } + + public abstract class WithImagesPreloaded : MultiImageBenchmarkBase + { + protected override void ReadImagesImpl() + { + base.ReadImagesImpl(); + + foreach (var kv in this.FileNamesToBytes) + { + byte[] bytes = kv.Value; + string fn = kv.Key; + + using (var ms1 = new MemoryStream(bytes)) + { + this.FileNamesToImageSharpImages[fn] = new Image(ms1); + + } + + this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); + } + } + + protected IEnumerable> FileNames2ImageSharpImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToImageSharpImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); + + protected IEnumerable> FileNames2SystemDrawingImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToSystemDrawingImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); + + protected virtual int LargeImageThresholdInPixels => 700000; + + protected void ForEachImageSharpImage(Func operation) + { + foreach (var kv in this.FileNames2ImageSharpImages) + { + try + { + var obj = operation(kv.Value); + (obj as IDisposable)?.Dispose(); + + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + } + + } + } + + protected void ForEachSystemDrawingImage(Func operation) + { + foreach (var kv in this.FileNames2SystemDrawingImages) + { + try + { + var obj = operation(kv.Value); + (obj as IDisposable)?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + } + } + } + } + + + } + + +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 265d6b5f2..d117cfd0a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -120,25 +120,6 @@ namespace ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); } - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Fdct_SimdReferenceImplementation_IsEquivalentToFloatingPointReferenceImplementation(int seed) - { - Block classic = new Block() { Data = Create8x8RandomIntData(-200, 200, seed) }; - MutableSpan src = new MutableSpan(classic.Data).ConvertToFloat32MutableSpan(); - - MutableSpan dest1 = new MutableSpan(64); - MutableSpan dest2 = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(src, dest1, temp, downscaleBy8: true, offsetSourceByNeg128: false); - ReferenceImplementations.fDCT8x8_llm_sse(src, dest2, temp); - - Assert.Equal(dest1.Data, dest2.Data, new ApproximateFloatComparer(1f)); - } + } } } \ No newline at end of file From c10ff894e76b6d90467521f4a7a5ad22ded522c0 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sat, 24 Dec 2016 16:09:05 +0100 Subject: [PATCH 12/15] JpegEncoderCore has "only" 862 lines now --- .../Formats/Jpg/Components/Block8x8F.cs | 18 +- .../Formats/Jpg/Components/HuffIndex.cs | 32 + .../Formats/Jpg/Components/HuffmanLut.cs | 68 ++ .../Formats/Jpg/Components/HuffmanSpec.cs | 112 +++ .../Formats/Jpg/Components/QuantIndex.cs | 18 + src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 659 ++++++------------ .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 2 +- .../Jpg/ReferenceImplementationsTests.cs | 145 ---- .../ImageSharp.Tests46.csproj | 4 +- 9 files changed, 473 insertions(+), 585 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs create mode 100644 src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs create mode 100644 src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs create mode 100644 src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs delete mode 100644 tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 66ef146e4..cfea24ad9 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -103,6 +103,22 @@ namespace ImageSharp.Formats Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount); } + /// + /// Convert salars to byte-s and copy to dest + /// + /// Pointer to block + /// Destination + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest) + { + float* fPtr = (float*)blockPtr; + for (int i = 0; i < ScalarCount; i++) + { + dest[i] = (byte) *fPtr; + fPtr++; + } + } + /// /// Load raw 32bit floating point data from source /// @@ -128,7 +144,7 @@ namespace ImageSharp.Formats Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount); } } - + /// /// Copy raw 32bit floating point data to dest /// diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs b/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs new file mode 100644 index 000000000..ff2a888a4 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs @@ -0,0 +1,32 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// Enumerates the Huffman tables + /// + internal enum HuffIndex + { + /// + /// The DC luminance huffman table index + /// + LuminanceDC = 0, + + // ReSharper disable UnusedMember.Local + + /// + /// The AC luminance huffman table index + /// + LuminanceAC = 1, + + /// + /// The DC chrominance huffman table index + /// + ChrominanceDC = 2, + + /// + /// The AC chrominance huffman table index + /// + ChrominanceAC = 3, + + // ReSharper restore UnusedMember.Local + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs b/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs new file mode 100644 index 000000000..a53ebf61b --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs @@ -0,0 +1,68 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// A compiled look-up table representation of a huffmanSpec. + /// Each value maps to a uint32 of which the 8 most significant bits hold the + /// codeword size in bits and the 24 least significant bits hold the codeword. + /// The maximum codeword size is 16 bits. + /// + internal struct HuffmanLut + { + /// + /// Initializes a new instance of the class. + /// + /// The encoding specifications. + public HuffmanLut(HuffmanSpec spec) + { + int maxValue = 0; + + foreach (byte v in spec.Values) + { + if (v > maxValue) + { + maxValue = v; + } + } + + this.Values = new uint[maxValue + 1]; + + int code = 0; + int k = 0; + + for (int i = 0; i < spec.Count.Length; i++) + { + int bits = (i + 1) << 24; + for (int j = 0; j < spec.Count[i]; j++) + { + this.Values[spec.Values[k]] = (uint)(bits | code); + code++; + k++; + } + + code <<= 1; + } + } + + /// + /// Initialize static members + /// + static HuffmanLut() + { + // Initialize the Huffman tables + for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++) + { + HuffmanLut.TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); + } + } + + /// + /// Gets the collection of huffman values. + /// + public uint[] Values { get; } + + /// + /// The compiled representations of theHuffmanSpec. + /// + public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs new file mode 100644 index 000000000..909d7da03 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs @@ -0,0 +1,112 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// The Huffman encoding specifications. + /// + internal struct HuffmanSpec + { + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; + + /// + /// Gets value[i] - The decoded value of the codeword at the given index. + /// + public readonly byte[] Values; + + /// + /// Initializes a new instance of the struct. + /// + /// The number of codes. + /// The decoded values. + public HuffmanSpec(byte[] count, byte[] values) + { + this.Count = count; + this.Values = values; + } + +#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines + /// + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. + /// + public static readonly HuffmanSpec[] TheHuffmanSpecs = + { + // Luminance DC. + new HuffmanSpec( + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }), + new HuffmanSpec( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), + + // Chrominance AC. + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }) + }; +#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs b/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs new file mode 100644 index 000000000..11a82f831 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs @@ -0,0 +1,18 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// Enumerates the quantization tables + /// + internal enum QuantIndex + { + /// + /// The luminance quantization table index + /// + Luminance = 0, + + /// + /// The chrominance quantization table index + /// + Chrominance = 1, + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 8c5ed8b90..75c38ae5a 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -2,14 +2,16 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp.Formats { using System; + using System.Buffers; using System.IO; using System.Numerics; using System.Runtime.CompilerServices; + using ImageSharp.Formats.Jpg.Components; + /// /// Image encoder for writing an image to a stream as a jpeg. /// @@ -20,95 +22,6 @@ namespace ImageSharp.Formats /// private const int QuantizationTableCount = 2; -#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines - - /// - /// The Huffman encoding specifications. - /// This encoder uses the same Huffman encoding for all images. - /// - private static readonly HuffmanSpec[] TheHuffmanSpecs = - { - // Luminance DC. - new HuffmanSpec( - new byte[] - { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 - }, - new byte[] - { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, - 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, - 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, - 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, - 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, - 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, - 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, - 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, - 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, - 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa - }), - new HuffmanSpec( - new byte[] - { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), - - // Chrominance AC. - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 - }, - new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, - 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, - 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, - 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, - 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, - 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, - 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, - 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, - 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, - 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa - }) - }; -#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines - - /// - /// The compiled representations of theHuffmanSpec. - /// - private static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; - /// /// Counts the number of bits needed to hold an integer. /// @@ -129,32 +42,6 @@ namespace ImageSharp.Formats 8, 8, 8, }; - /// - /// The unscaled quantization tables in zig-zag order. Each - /// encoder copies and scales the tables according to its quality parameter. - /// The values are derived from section K.1 after converting from natural to - /// zig-zag order. - /// - private static readonly byte[,] UnscaledQuant = - { - { - // Luminance. - 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, - 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, - 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, - 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, - 100, 120, 92, 101, 103, 99, - }, - { - // Chrominance. - 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - } - }; - /// /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: /// - the marker length "\x00\x0c", @@ -168,7 +55,9 @@ namespace ImageSharp.Formats /// private static readonly byte[] SosHeaderYCbCr = { - JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, // Marker + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, + + // Marker 0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) @@ -181,7 +70,35 @@ namespace ImageSharp.Formats 0x11, // DC/AC Huffman table 0x00, // Ss - Start of spectral selection. 0x3f, // Se - End of spectral selection. - 0x00 // Ah + Ah (Successive approximation bit position high + low) + 0x00 + + // Ah + Ah (Successive approximation bit position high + low) + }; + + /// + /// The unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + private static readonly byte[,] UnscaledQuant = + { + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, + 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, + 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, + 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, + 100, 120, 92, 101, 103, 99, + }, + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + } }; /// @@ -199,16 +116,6 @@ namespace ImageSharp.Formats /// private readonly byte[] huffmanBuffer = new byte[179]; - /// - /// The scaled luminance table, in zig-zag order. - /// - private Block8x8F luminanceQuantTable; - - /// - /// The scaled chrominance table, in zig-zag order. - /// - private Block8x8F chrominanceQuantTable; - /// /// The accumulated bits to write to the stream. /// @@ -220,72 +127,24 @@ namespace ImageSharp.Formats private uint bitCount; /// - /// The output stream. All attempted writes after the first error become no-ops. - /// - private Stream outputStream; - - /// - /// The subsampling method to use. + /// The scaled chrominance table, in zig-zag order. /// - private JpegSubsample subsample; + private Block8x8F chrominanceQuantTable; /// - /// Initializes static members of the class. + /// The scaled luminance table, in zig-zag order. /// - static JpegEncoderCore() - { - // Initialize the Huffman tables - for (int i = 0; i < TheHuffmanSpecs.Length; i++) - { - TheHuffmanLut[i] = new HuffmanLut(TheHuffmanSpecs[i]); - } - } + private Block8x8F luminanceQuantTable; /// - /// Enumerates the Huffman tables + /// The output stream. All attempted writes after the first error become no-ops. /// - private enum HuffIndex - { - /// - /// The DC luminance huffman table index - /// - LuminanceDC = 0, - - // ReSharper disable UnusedMember.Local - - /// - /// The AC luminance huffman table index - /// - LuminanceAC = 1, - - /// - /// The DC chrominance huffman table index - /// - ChrominanceDC = 2, - - /// - /// The AC chrominance huffman table index - /// - ChrominanceAC = 3, - - // ReSharper restore UnusedMember.Local - } + private Stream outputStream; /// - /// Enumerates the quantization tables + /// The subsampling method to use. /// - private enum QuantIndex - { - /// - /// The luminance quantization table index - /// - Luminance = 0, - - /// - /// The chrominance quantization table index - /// - Chrominance = 1, - } + private JpegSubsample subsample; /// /// Encode writes the image to the jpeg baseline format with the given options. @@ -403,8 +262,7 @@ namespace ImageSharp.Formats Block8x8F* yBlock, Block8x8F* cbBlock, Block8x8F* crBlock, - PixelArea rgbBytes) - where TColor : struct, IPackedPixel, IEquatable + PixelArea rgbBytes) where TColor : struct, IPackedPixel, IEquatable { float* yBlockRaw = (float*)yBlock; float* cbBlockRaw = (float*)cbBlock; @@ -438,6 +296,18 @@ namespace ImageSharp.Formats } } +#pragma warning disable SA1204 + private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) + { + dqt[offset++] = (byte)i; + for (int j = 0; j < Block8x8F.ScalarCount; j++) + { + dqt[offset++] = (byte)q[j]; + } + } + +#pragma warning restore SA1204 + /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits < 1<<nBits && nBits <= 16. @@ -486,7 +356,7 @@ namespace ImageSharp.Formats [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EmitHuff(HuffIndex index, int value) { - uint x = TheHuffmanLut[(int)index].Values[value]; + uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value]; this.Emit(x & ((1 << 24) - 1), x >> 24); } @@ -524,6 +394,107 @@ namespace ImageSharp.Formats } } + /// + /// Encodes the image with no subsampling. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + private void Encode444(PixelAccessor pixels) + where TColor : struct, IPackedPixel, IEquatable + { + // TODO: Need a JpegEncoderScanImpl struct to encapsulate all this mess: + Block8x8F b = default(Block8x8F); + Block8x8F cb = default(Block8x8F); + Block8x8F cr = default(Block8x8F); + + Block8x8F temp1 = default(Block8x8F); + Block8x8F temp2 = default(Block8x8F); + + Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; + Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; + + UnzigData unzig = UnzigData.Create(); + + // ReSharper disable once InconsistentNaming + float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true)) + { + for (int y = 0; y < pixels.Height; y += 8) + { + for (int x = 0; x < pixels.Width; x += 8) + { + ToYCbCr(pixels, x, y, &b, &cb, &cr, rgbBytes); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &b, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &cb, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &cr, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + } + } + } + } + + /// + /// Writes the application header containing the JFIF identifier plus extra data. + /// + /// The resolution of the image in the x- direction. + /// The resolution of the image in the y- direction. + private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) + { + // Write the start of image marker. Markers are always prefixed with with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.SOI; + + // Write the JFIF headers + this.buffer[2] = JpegConstants.Markers.XFF; + this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[4] = 0x00; + this.buffer[5] = 0x10; + this.buffer[6] = 0x4a; // J + this.buffer[7] = 0x46; // F + this.buffer[8] = 0x49; // I + this.buffer[9] = 0x46; // F + this.buffer[10] = 0x00; // = "JFIF",'\0' + this.buffer[11] = 0x01; // versionhi + this.buffer[12] = 0x01; // versionlo + this.buffer[13] = 0x01; // xyunits as dpi + + // No thumbnail + this.buffer[14] = 0x00; // Thumbnail width + this.buffer[15] = 0x00; // Thumbnail height + + this.outputStream.Write(this.buffer, 0, 16); + + // Resolution. Big Endian + this.buffer[0] = (byte)(horizontalResolution >> 8); + this.buffer[1] = (byte)horizontalResolution; + this.buffer[2] = (byte)(verticalResolution >> 8); + this.buffer[3] = (byte)verticalResolution; + + this.outputStream.Write(this.buffer, 0, 4); + } + /// /// Writes a block of pixel data using the given quantization table, /// returning the post-quantized DC value of the DCT-transformed block. @@ -591,54 +562,74 @@ namespace ImageSharp.Formats } /// - /// Writes the application header containing the JFIF identifier plus extra data. + /// Writes the Define Huffman Table marker and tables. /// - /// The resolution of the image in the x- direction. - /// The resolution of the image in the y- direction. - private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) + /// The number of components to write. + private void WriteDefineHuffmanTables(int componentCount) { - // Write the start of image marker. Markers are always prefixed with with 0xff. - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.SOI; + // Table identifiers. + byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; + int markerlen = 2; + HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs; - // Write the JFIF headers - this.buffer[2] = JpegConstants.Markers.XFF; - this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker - this.buffer[4] = 0x00; - this.buffer[5] = 0x10; - this.buffer[6] = 0x4a; // J - this.buffer[7] = 0x46; // F - this.buffer[8] = 0x49; // I - this.buffer[9] = 0x46; // F - this.buffer[10] = 0x00; // = "JFIF",'\0' - this.buffer[11] = 0x01; // versionhi - this.buffer[12] = 0x01; // versionlo - this.buffer[13] = 0x01; // xyunits as dpi + if (componentCount == 1) + { + // Drop the Chrominance tables. + specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] }; + } - // No thumbnail - this.buffer[14] = 0x00; // Thumbnail width - this.buffer[15] = 0x00; // Thumbnail height + foreach (HuffmanSpec s in specs) + { + markerlen += 1 + 16 + s.Values.Length; + } - this.outputStream.Write(this.buffer, 0, 16); + this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); + for (int i = 0; i < specs.Length; i++) + { + HuffmanSpec spec = specs[i]; + int len = 0; - // Resolution. Big Endian - this.buffer[0] = (byte)(horizontalResolution >> 8); - this.buffer[1] = (byte)horizontalResolution; - this.buffer[2] = (byte)(verticalResolution >> 8); - this.buffer[3] = (byte)verticalResolution; + fixed (byte* huffman = this.huffmanBuffer) + fixed (byte* count = spec.Count) + fixed (byte* values = spec.Values) + { + huffman[len++] = headers[i]; - this.outputStream.Write(this.buffer, 0, 4); + for (int c = 0; c < spec.Count.Length; c++) + { + huffman[len++] = count[c]; + } + + for (int v = 0; v < spec.Values.Length; v++) + { + huffman[len++] = values[v]; + } + } + + this.outputStream.Write(this.huffmanBuffer, 0, len); + } } /// - /// Writes the metadata profiles to the image. + /// Writes the Define Quantization Marker and tables. /// - /// The image. - /// The pixel format. - private void WriteProfiles(Image image) - where TColor : struct, IPackedPixel, IEquatable + private void WriteDefineQuantizationTables() { - this.WriteProfile(image.ExifProfile); + // Marker + quantization table lengths + int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount)); + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); + + // Loop through and collect the tables as one array. + // This allows us to reduce the number of writes to the stream. + int dqtCount = (QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount; + byte[] dqt = ArrayPool.Shared.Rent(dqtCount); + int offset = 0; + + WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); + WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); + + this.outputStream.Write(dqt, 0, dqtCount); + ArrayPool.Shared.Return(dqt); } /// @@ -674,35 +665,14 @@ namespace ImageSharp.Formats } /// - /// Writes the Define Quantization Marker and tables. + /// Writes the metadata profiles to the image. /// - private void WriteDefineQuantizationTables() - { - // Marker + quantization table lengths - int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount)); - this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - - // Loop through and collect the tables as one array. - // This allows us to reduce the number of writes to the stream. - byte[] dqt = new byte[(QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount]; - int offset = 0; - - WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); - WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); - - this.outputStream.Write(dqt, 0, dqt.Length); - } - -#pragma warning disable SA1204 - private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) + /// The image. + /// The pixel format. + private void WriteProfiles(Image image) where TColor : struct, IPackedPixel, IEquatable { - dqt[offset++] = (byte)i; - for (int j = 0; j < Block8x8F.ScalarCount; j++) - { - dqt[offset++] = (byte)q[j]; - } + this.WriteProfile(image.ExifProfile); } -#pragma warning restore SA1204 /// /// Writes the Start Of Frame (Baseline) marker @@ -760,55 +730,6 @@ namespace ImageSharp.Formats this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); } - /// - /// Writes the Define Huffman Table marker and tables. - /// - /// The number of components to write. - private void WriteDefineHuffmanTables(int componentCount) - { - // Table identifiers. - byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; - int markerlen = 2; - HuffmanSpec[] specs = TheHuffmanSpecs; - - if (componentCount == 1) - { - // Drop the Chrominance tables. - specs = new[] { TheHuffmanSpecs[0], TheHuffmanSpecs[1] }; - } - - foreach (HuffmanSpec s in specs) - { - markerlen += 1 + 16 + s.Values.Length; - } - - this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); - for (int i = 0; i < specs.Length; i++) - { - HuffmanSpec spec = specs[i]; - int len = 0; - - fixed (byte* huffman = this.huffmanBuffer) - fixed (byte* count = spec.Count) - fixed (byte* values = spec.Values) - { - huffman[len++] = headers[i]; - - for (int c = 0; c < spec.Count.Length; c++) - { - huffman[len++] = count[c]; - } - - for (int v = 0; v < spec.Values.Length; v++) - { - huffman[len++] = values[v]; - } - } - - this.outputStream.Write(this.huffmanBuffer, 0, len); - } - } - /// /// Writes the StartOfScan marker. /// @@ -819,6 +740,7 @@ namespace ImageSharp.Formats private void WriteStartOfScan(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { + // TODO: This method should be the entry point for a JpegEncoderScanImpl struct // TODO: We should allow grayscale writing. this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); @@ -836,67 +758,6 @@ namespace ImageSharp.Formats this.Emit(0x7f, 7); } - /// - /// Encodes the image with no subsampling. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - private void Encode444(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable - { - // TODO: Need a JpegEncoderCoreCore class or struct to hold all this mess: - Block8x8F b = default(Block8x8F); - Block8x8F cb = default(Block8x8F); - Block8x8F cr = default(Block8x8F); - - Block8x8F temp1 = default(Block8x8F); - Block8x8F temp2 = default(Block8x8F); - - Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; - Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - - UnzigData unzig = UnzigData.Create(); - - // ReSharper disable once InconsistentNaming - float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true)) - { - for (int y = 0; y < pixels.Height; y += 8) - { - for (int x = 0; x < pixels.Width; x += 8) - { - ToYCbCr(pixels, x, y, &b, &cb, &cr, rgbBytes); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - &b, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &cb, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &cr, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - } - } - } - } - #pragma warning disable SA1201 // MethodShouldNotFollowAStruct /// @@ -916,7 +777,7 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: Need a JpegEncoderCoreCore class or struct to hold all this mess: + // TODO: Need a JpegEncoderScanImpl struct to encapsulate all this mess: Block8x8F b = default(Block8x8F); BlockQuad cb = default(BlockQuad); @@ -997,81 +858,5 @@ namespace ImageSharp.Formats this.buffer[3] = (byte)(length & 0xff); this.outputStream.Write(this.buffer, 0, 4); } - - /// - /// The Huffman encoding specifications. - /// - private struct HuffmanSpec - { - /// - /// Gets count[i] - The number of codes of length i bits. - /// - public readonly byte[] Count; - - /// - /// Gets value[i] - The decoded value of the codeword at the given index. - /// - public readonly byte[] Values; - - /// - /// Initializes a new instance of the struct. - /// - /// The number of codes. - /// The decoded values. - public HuffmanSpec(byte[] count, byte[] values) - { - this.Count = count; - this.Values = values; - } - } - - /// - /// A compiled look-up table representation of a huffmanSpec. - /// Each value maps to a uint32 of which the 8 most significant bits hold the - /// codeword size in bits and the 24 least significant bits hold the codeword. - /// The maximum codeword size is 16 bits. - /// - private class HuffmanLut - { - /// - /// Initializes a new instance of the class. - /// - /// The encoding specifications. - public HuffmanLut(HuffmanSpec spec) - { - int maxValue = 0; - - foreach (byte v in spec.Values) - { - if (v > maxValue) - { - maxValue = v; - } - } - - this.Values = new uint[maxValue + 1]; - - int code = 0; - int k = 0; - - for (int i = 0; i < spec.Count.Length; i++) - { - int bits = (i + 1) << 24; - for (int j = 0; j < spec.Count[i]; j++) - { - this.Values[spec.Values[k]] = (uint)(bits | code); - code++; - k++; - } - - code <<= 1; - } - } - - /// - /// Gets the collection of huffman values. - /// - public uint[] Values { get; } - } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 48d5454f6..a84c64aba 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -72,7 +72,7 @@ namespace ImageSharp.Tests //PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb; [Theory( - //Skip = "Benchmark, enable manually!" + Skip = "Benchmark, enable manually!" )] [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)] [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio444, 75)] diff --git a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs deleted file mode 100644 index e30baddfc..000000000 --- a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs +++ /dev/null @@ -1,145 +0,0 @@ -// ReSharper disable InconsistentNaming -namespace ImageSharp.Tests -{ - using System.Numerics; - using ImageSharp.Formats; - - using Xunit; - using Xunit.Abstractions; - - public class ReferenceImplementationsTests : UtilityTestClassBase - { - public ReferenceImplementationsTests(ITestOutputHelper output) - : base(output) - { - } - - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); - MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); - - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData); - - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp); - - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void IntegerDCT_ForwardThenInverse(int seed, int startAt) - { - MutableSpan original = Create8x8RandomIntData(-200, 200, seed); - - var block = original.AddScalarToAllValues(128); - - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block); - - for (int i = 0; i < 64; i++) - { - block[i] /= 8; - } - - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block); - - for (int i = startAt; i < 64; i++) - { - float expected = original[i]; - float actual = (float)block[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); - } - - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt) - { - var data = Create8x8RandomIntData(-200, 200, seed); - MutableSpan src = new MutableSpan(data).ConvertToFloat32MutableSpan(); - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(src, dest, temp, true); - ReferenceImplementations.iDCT2D_llm(dest, src, temp); - - for (int i = startAt; i < 64; i++) - { - float expected = data[i]; - float actual = (float)src[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); - } - } - - [Fact] - public void HowMuchIsTheFish() - { - Output.WriteLine(Vector.Count.ToString()); - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); - MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); - - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData); - - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); - - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Fdct_SimdReferenceImplementation_IsEquivalentToFloatingPointReferenceImplementation(int seed) - { - Block classic = new Block() { Data = Create8x8RandomIntData(-200, 200, seed) }; - MutableSpan src = new MutableSpan(classic.Data).ConvertToFloat32MutableSpan(); - - MutableSpan dest1 = new MutableSpan(64); - MutableSpan dest2 = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(src, dest1, temp, downscaleBy8: true, offsetSourceByNeg128: false); - ReferenceImplementations.fDCT8x8_llm_sse(src, dest2, temp); - - Assert.Equal(dest1.Data, dest2.Data, new ApproximateFloatComparer(1f)); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj index 2f03fa24c..d53ee3d9c 100644 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -82,6 +82,9 @@ Formats\Jpg\ReferenceImplementations.cs + + Formats\Jpg\ReferenceImplementationsTests.cs + Formats\Jpg\UtilityTestClassBase.cs @@ -157,7 +160,6 @@ TestUtilities\TestUtilityExtensions.cs - From b89f22c12139f3d31d17b050f037aec9c9027f21 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sat, 24 Dec 2016 16:28:57 +0100 Subject: [PATCH 13/15] moved Jpeg classes to separate folders --- .../Formats/Jpg/Components/Block8x8F.cs | 2 + .../Jpg/Components/{ => Decoder}/Bits.cs | 2 +- .../Jpg/Components/{ => Decoder}/Bytes.cs | 2 +- .../Jpg/Components/{ => Decoder}/Component.cs | 2 +- .../Jpg/Components/{ => Decoder}/GrayImage.cs | 2 +- .../{Huffman.cs => Decoder/HuffmanTree.cs} | 4 +- .../Components/{ => Decoder}/YCbCrImage.cs | 2 +- .../Jpg/Components/{ => Encoder}/HuffIndex.cs | 2 +- .../Components/{ => Encoder}/HuffmanLut.cs | 2 +- .../Components/{ => Encoder}/HuffmanSpec.cs | 2 +- .../Components/{ => Encoder}/QuantIndex.cs | 2 +- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 55 ++++++++++--------- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 11 ++-- .../Formats/Jpg/{ => Utils}/JpegUtils.cs | 2 +- .../Jpg/{Components => Utils}/MutableSpan.cs | 2 +- .../MutableSpanExtensions.cs | 2 +- .../Formats/Jpg/Block8x8FTests.cs | 1 + .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 11 ++-- .../Formats/Jpg/ReferenceImplementations.cs | 1 + .../Jpg/ReferenceImplementationsTests.cs | 2 + .../Formats/Jpg/UtilityTestClassBase.cs | 4 +- 21 files changed, 65 insertions(+), 50 deletions(-) rename src/ImageSharp/Formats/Jpg/Components/{ => Decoder}/Bits.cs (98%) rename src/ImageSharp/Formats/Jpg/Components/{ => Decoder}/Bytes.cs (99%) rename src/ImageSharp/Formats/Jpg/Components/{ => Decoder}/Component.cs (94%) rename src/ImageSharp/Formats/Jpg/Components/{ => Decoder}/GrayImage.cs (98%) rename src/ImageSharp/Formats/Jpg/Components/{Huffman.cs => Decoder/HuffmanTree.cs} (96%) rename src/ImageSharp/Formats/Jpg/Components/{ => Decoder}/YCbCrImage.cs (99%) rename src/ImageSharp/Formats/Jpg/Components/{ => Encoder}/HuffIndex.cs (92%) rename src/ImageSharp/Formats/Jpg/Components/{ => Encoder}/HuffmanLut.cs (97%) rename src/ImageSharp/Formats/Jpg/Components/{ => Encoder}/HuffmanSpec.cs (98%) rename src/ImageSharp/Formats/Jpg/Components/{ => Encoder}/QuantIndex.cs (86%) rename src/ImageSharp/Formats/Jpg/{ => Utils}/JpegUtils.cs (98%) rename src/ImageSharp/Formats/Jpg/{Components => Utils}/MutableSpan.cs (98%) rename src/ImageSharp/Formats/Jpg/{Components => Utils}/MutableSpanExtensions.cs (99%) diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index cfea24ad9..e0886e134 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -11,6 +11,8 @@ namespace ImageSharp.Formats using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using ImageSharp.Formats.Jpg.Utils; + /// /// DCT code Ported from https://github.com/norishigefukushima/dct_simd /// diff --git a/src/ImageSharp/Formats/Jpg/Components/Bits.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs similarity index 98% rename from src/ImageSharp/Formats/Jpg/Components/Bits.cs rename to src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs index d7449f4a3..c9624c444 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Bits.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Components.Decoder { using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/Jpg/Components/Bytes.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs similarity index 99% rename from src/ImageSharp/Formats/Jpg/Components/Bytes.cs rename to src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs index 127ba478b..55326a37c 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Bytes.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs @@ -2,7 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Components.Decoder { using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/Jpg/Components/Component.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs similarity index 94% rename from src/ImageSharp/Formats/Jpg/Components/Component.cs rename to src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs index f70dbff3f..29117e492 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Components.Decoder { /// /// Represents a single color component diff --git a/src/ImageSharp/Formats/Jpg/Components/GrayImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs similarity index 98% rename from src/ImageSharp/Formats/Jpg/Components/GrayImage.cs rename to src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs index 5f5f015ad..ccb42a1a6 100644 --- a/src/ImageSharp/Formats/Jpg/Components/GrayImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Components.Decoder { /// /// Represents a grayscale image diff --git a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs similarity index 96% rename from src/ImageSharp/Formats/Jpg/Components/Huffman.cs rename to src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs index c0d5a5caa..7d5932b21 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs @@ -2,7 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Components.Decoder { using System; using System.Buffers; @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// /// Represents a Huffman tree /// - internal struct Huffman : IDisposable + internal struct HuffmanTree : IDisposable { /// /// Gets or sets the number of codes in the tree. diff --git a/src/ImageSharp/Formats/Jpg/Components/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs similarity index 99% rename from src/ImageSharp/Formats/Jpg/Components/YCbCrImage.cs rename to src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs index 603324bcc..018150aeb 100644 --- a/src/ImageSharp/Formats/Jpg/Components/YCbCrImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Components.Decoder { /// /// Represents an image made up of three color components (luminance, blue chroma, red chroma) diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs similarity index 92% rename from src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs rename to src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs index ff2a888a4..a4265ea8e 100644 --- a/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs @@ -1,4 +1,4 @@ -namespace ImageSharp.Formats.Jpg.Components +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// /// Enumerates the Huffman tables diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs similarity index 97% rename from src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs rename to src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs index a53ebf61b..93b412b25 100644 --- a/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs @@ -1,4 +1,4 @@ -namespace ImageSharp.Formats.Jpg.Components +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// /// A compiled look-up table representation of a huffmanSpec. diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs similarity index 98% rename from src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs rename to src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs index 909d7da03..2e856628a 100644 --- a/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs @@ -1,4 +1,4 @@ -namespace ImageSharp.Formats.Jpg.Components +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// /// The Huffman encoding specifications. diff --git a/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs similarity index 86% rename from src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs rename to src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs index 11a82f831..192e29f08 100644 --- a/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs @@ -1,4 +1,4 @@ -namespace ImageSharp.Formats.Jpg.Components +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// /// Enumerates the quantization tables diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index b74ae2317..9e1ab8d00 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -11,6 +11,9 @@ namespace ImageSharp.Formats using System.Runtime.InteropServices; using System.Threading.Tasks; + using ImageSharp.Formats.Jpg.Components.Decoder; + using ImageSharp.Formats.Jpg.Utils; + /// /// Performs the jpeg decoding operation. /// @@ -76,7 +79,7 @@ namespace ImageSharp.Formats /// /// The huffman trees /// - private readonly Huffman[] huffmanTrees; + private readonly HuffmanTree[] huffmanTrees; /// /// Quantization tables, in zigzag order. @@ -186,7 +189,7 @@ namespace ImageSharp.Formats public JpegDecoderCore() { // this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1]; - this.huffmanTrees = new Huffman[(MaxTc + 1) * (MaxTh + 1)]; + this.huffmanTrees = new HuffmanTree[(MaxTc + 1) * (MaxTh + 1)]; this.quantizationTables = new Block8x8F[MaxTq + 1]; this.temp = new byte[2 * Block8x8F.ScalarCount]; @@ -546,42 +549,42 @@ namespace ImageSharp.Formats } } - private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining) + private void ProcessDefineHuffmanTablesMarkerLoop(ref HuffmanTree huffmanTree, ref int remaining) { // Read nCodes and huffman.Valuess (and derive h.Length). // nCodes[i] is the number of codes with code length i. // h.Length is the total number of codes. - huffman.Length = 0; + huffmanTree.Length = 0; int[] ncodes = new int[MaxCodeLength]; for (int i = 0; i < ncodes.Length; i++) { ncodes[i] = this.temp[i + 1]; - huffman.Length += ncodes[i]; + huffmanTree.Length += ncodes[i]; } - if (huffman.Length == 0) + if (huffmanTree.Length == 0) { throw new ImageFormatException("Huffman table has zero length"); } - if (huffman.Length > MaxNCodes) + if (huffmanTree.Length > MaxNCodes) { throw new ImageFormatException("Huffman table has excessive length"); } - remaining -= huffman.Length + 17; + remaining -= huffmanTree.Length + 17; if (remaining < 0) { throw new ImageFormatException("DHT has wrong length"); } - this.ReadFull(huffman.Values, 0, huffman.Length); + this.ReadFull(huffmanTree.Values, 0, huffmanTree.Length); // Derive the look-up table. - for (int i = 0; i < huffman.Lut.Length; i++) + for (int i = 0; i < huffmanTree.Lut.Length; i++) { - huffman.Lut[i] = 0; + huffmanTree.Lut[i] = 0; } uint x = 0, code = 0; @@ -598,11 +601,11 @@ namespace ImageSharp.Formats // The high 8 bits of lutValue are the encoded value. // The low 8 bits are 1 plus the codeLength. byte base2 = (byte)(code << (7 - i)); - ushort lutValue = (ushort)((huffman.Values[x] << 8) | (2 + i)); + ushort lutValue = (ushort)((huffmanTree.Values[x] << 8) | (2 + i)); for (int k = 0; k < 1 << (7 - i); k++) { - huffman.Lut[base2 | k] = lutValue; + huffmanTree.Lut[base2 | k] = lutValue; } code++; @@ -617,15 +620,15 @@ namespace ImageSharp.Formats int nc = ncodes[i]; if (nc == 0) { - huffman.MinCodes[i] = -1; - huffman.MaxCodes[i] = -1; - huffman.Indices[i] = -1; + huffmanTree.MinCodes[i] = -1; + huffmanTree.MaxCodes[i] = -1; + huffmanTree.Indices[i] = -1; } else { - huffman.MinCodes[i] = c; - huffman.MaxCodes[i] = c + nc - 1; - huffman.Indices[i] = index; + huffmanTree.MinCodes[i] = c; + huffmanTree.MaxCodes[i] = c + nc - 1; + huffmanTree.Indices[i] = index; c += nc; index += nc; } @@ -637,12 +640,12 @@ namespace ImageSharp.Formats /// /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. /// - /// The huffman value + /// The huffman value /// The - private byte DecodeHuffman(ref Huffman huffman) + private byte DecodeHuffman(ref HuffmanTree huffmanTree) { // Copy stuff to the stack: - if (huffman.Length == 0) + if (huffmanTree.Length == 0) { throw new ImageFormatException("Uninitialized Huffman table"); } @@ -653,7 +656,7 @@ namespace ImageSharp.Formats if (errorCode == ErrorCodes.NoError) { - ushort v = huffman.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff]; + ushort v = huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff]; if (v != 0) { @@ -689,9 +692,9 @@ namespace ImageSharp.Formats this.bits.UnreadBits--; this.bits.Mask >>= 1; - if (code <= huffman.MaxCodes[i]) + if (code <= huffmanTree.MaxCodes[i]) { - return huffman.Values[huffman.Indices[i] + code - huffman.MinCodes[i]]; + return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]]; } code <<= 1; @@ -1927,7 +1930,7 @@ namespace ImageSharp.Formats /// The zig-zag start index /// The zig-zag end index /// The low transform offset - private void Refine(Block8x8F* b, ref Huffman h, int* unzigPtr, int zigStart, int zigEnd, int delta) + private void Refine(Block8x8F* b, ref HuffmanTree h, int* unzigPtr, int zigStart, int zigEnd, int delta) { // Refining a DC component is trivial. if (zigStart == 0) diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 75c38ae5a..241db3a64 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -11,6 +11,8 @@ namespace ImageSharp.Formats using System.Runtime.CompilerServices; using ImageSharp.Formats.Jpg.Components; + using ImageSharp.Formats.Jpg.Components.Encoder; + using ImageSharp.Formats.Jpg.Utils; /// /// Image encoder for writing an image to a stream as a jpeg. @@ -402,7 +404,7 @@ namespace ImageSharp.Formats private void Encode444(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: Need a JpegEncoderScanImpl struct to encapsulate all this mess: + // TODO: Need a JpegEncoderScanProcessor struct to encapsulate all this mess: Block8x8F b = default(Block8x8F); Block8x8F cb = default(Block8x8F); Block8x8F cr = default(Block8x8F); @@ -740,7 +742,7 @@ namespace ImageSharp.Formats private void WriteStartOfScan(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: This method should be the entry point for a JpegEncoderScanImpl struct + // TODO: This method should be the entry point for a JpegEncoderScanProcessor struct // TODO: We should allow grayscale writing. this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); @@ -761,7 +763,8 @@ namespace ImageSharp.Formats #pragma warning disable SA1201 // MethodShouldNotFollowAStruct /// - /// This struct belongs to Encode420. Much easeier to understand code if they are together. Why should I move it Up? :P + /// Poor man's stackalloc for Encode420. + /// This struct belongs to Encode420. Much easeier to understand code if they are close to each other. Why should I move it Up? :P /// private struct BlockQuad { @@ -777,7 +780,7 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: Need a JpegEncoderScanImpl struct to encapsulate all this mess: + // TODO: Need a JpegEncoderScanProcessor struct to encapsulate all this mess: Block8x8F b = default(Block8x8F); BlockQuad cb = default(BlockQuad); diff --git a/src/ImageSharp/Formats/Jpg/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs similarity index 98% rename from src/ImageSharp/Formats/Jpg/JpegUtils.cs rename to src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs index 67b33ea25..dd0e1c33c 100644 --- a/src/ImageSharp/Formats/Jpg/JpegUtils.cs +++ b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs @@ -2,7 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Utils { using System; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs b/src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs similarity index 98% rename from src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs rename to src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs index 77ee17fc2..f4c1468d6 100644 --- a/src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs +++ b/src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Utils { using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs b/src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs similarity index 99% rename from src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs rename to src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs index 31df4863c..a725c3e93 100644 --- a/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs +++ b/src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp.Formats.Jpg.Utils { using System.Numerics; using System.Runtime.CompilerServices; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 7d66f6347..ea198350b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -9,6 +9,7 @@ namespace ImageSharp.Tests using System.Numerics; using ImageSharp.Formats; + using ImageSharp.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index a84c64aba..13cf515bb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -11,6 +11,8 @@ namespace ImageSharp.Tests { using System.Numerics; + using ImageSharp.Formats.Jpg.Utils; + public class JpegTests { private ITestOutputHelper Output { get; } @@ -68,12 +70,11 @@ namespace ImageSharp.Tests TestImages.Jpeg.Cmyk }; - private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; - //PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb; + private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; //PixelTypes.Color | PixelTypes.Argb; + + - [Theory( - Skip = "Benchmark, enable manually!" - )] + //[Theory] // Benchmark, enable manually [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)] [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio444, 75)] public void Benchmark_JpegEncoder(TestImageProvider provider, JpegSubsample subSample, int quality) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index 860c1b55e..bdd66d1d1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Tests using System.Runtime.CompilerServices; using ImageSharp.Formats; + using ImageSharp.Formats.Jpg.Utils; /// /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index d117cfd0a..aadbf2c62 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -3,6 +3,8 @@ namespace ImageSharp.Tests.Formats.Jpg { using System.Numerics; using ImageSharp.Formats; + using ImageSharp.Formats.Jpg.Utils; + using Xunit; using Xunit.Abstractions; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs index 6191d221c..a52395f17 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs @@ -9,7 +9,9 @@ namespace ImageSharp.Tests using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; - + + using ImageSharp.Formats.Jpg.Utils; + /// /// Utility class to measure the execution of an operation. /// From 856535cf445596e97678ba3bb56e82fca041ddca Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sat, 24 Dec 2016 16:33:49 +0100 Subject: [PATCH 14/15] moved WriteDataToDqt --- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 16 ++-------------- src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 241db3a64..06e722024 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -298,18 +298,6 @@ namespace ImageSharp.Formats } } -#pragma warning disable SA1204 - private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) - { - dqt[offset++] = (byte)i; - for (int j = 0; j < Block8x8F.ScalarCount; j++) - { - dqt[offset++] = (byte)q[j]; - } - } - -#pragma warning restore SA1204 - /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits < 1<<nBits && nBits <= 16. @@ -627,8 +615,8 @@ namespace ImageSharp.Formats byte[] dqt = ArrayPool.Shared.Rent(dqtCount); int offset = 0; - WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); - WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); + JpegUtils.WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); + JpegUtils.WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); this.outputStream.Write(dqt, 0, dqtCount); ArrayPool.Shared.Return(dqt); diff --git a/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs index dd0e1c33c..f4c2fa2c4 100644 --- a/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs +++ b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Formats.Jpg.Utils using System; using System.Runtime.CompilerServices; + using ImageSharp.Formats.Jpg.Components.Encoder; + /// /// Jpeg specific utilities and extension methods /// @@ -89,5 +91,14 @@ namespace ImageSharp.Formats.Jpg.Utils { return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; } + + internal static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) + { + dqt[offset++] = (byte)i; + for (int j = 0; j < Block8x8F.ScalarCount; j++) + { + dqt[offset++] = (byte)q[j]; + } + } } } \ No newline at end of file From f3a3391cd610e48b58ea897fee7b84f0160e7ac3 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sun, 25 Dec 2016 01:37:31 +0100 Subject: [PATCH 15/15] Style Stalin --- .../Formats/Jpg/Components/Block8x8F.cs | 6 +- .../Jpg/Components/Decoder/HuffmanTree.cs | 2 +- .../Jpg/Components/Encoder/HuffIndex.cs | 6 +- .../Jpg/Components/Encoder/HuffmanLut.cs | 45 ++-- .../Jpg/Components/Encoder/HuffmanSpec.cs | 166 +++++++----- .../Jpg/Components/Encoder/QuantIndex.cs | 12 +- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 253 ++++++++++++------ src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs | 80 ++++-- 8 files changed, 366 insertions(+), 204 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index e0886e134..4cd852f10 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -3,7 +3,6 @@ // Licensed under the Apache License, Version 2.0. // // ReSharper disable InconsistentNaming - namespace ImageSharp.Formats { using System; @@ -19,6 +18,7 @@ namespace ImageSharp.Formats internal partial struct Block8x8F { #pragma warning disable SA1204 // Static members must appear before non-static members + /// /// Vector count /// @@ -116,7 +116,7 @@ namespace ImageSharp.Formats float* fPtr = (float*)blockPtr; for (int i = 0; i < ScalarCount; i++) { - dest[i] = (byte) *fPtr; + dest[i] = (byte)*fPtr; fPtr++; } } @@ -146,7 +146,7 @@ namespace ImageSharp.Formats Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount); } } - + /// /// Copy raw 32bit floating point data to dest /// diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs index 7d5932b21..744ed363b 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs index a4265ea8e..d53ac90a8 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs @@ -1,4 +1,8 @@ -namespace ImageSharp.Formats.Jpg.Components.Encoder +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// /// Enumerates the Huffman tables diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs index 93b412b25..ec6c86041 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs @@ -1,4 +1,9 @@ -namespace ImageSharp.Formats.Jpg.Components.Encoder +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// /// A compiled look-up table representation of a huffmanSpec. @@ -9,9 +14,26 @@ internal struct HuffmanLut { /// - /// Initializes a new instance of the class. + /// The compiled representations of theHuffmanSpec. + /// + public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; + + /// + /// Initializes static members of the struct. + /// + static HuffmanLut() + { + // Initialize the Huffman tables + for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++) + { + TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); + } + } + + /// + /// Initializes a new instance of the struct. /// - /// The encoding specifications. + /// dasd public HuffmanLut(HuffmanSpec spec) { int maxValue = 0; @@ -43,26 +65,9 @@ } } - /// - /// Initialize static members - /// - static HuffmanLut() - { - // Initialize the Huffman tables - for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++) - { - HuffmanLut.TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); - } - } - /// /// Gets the collection of huffman values. /// public uint[] Values { get; } - - /// - /// The compiled representations of theHuffmanSpec. - /// - public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs index 2e856628a..25d0fcfb1 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs @@ -1,35 +1,19 @@ -namespace ImageSharp.Formats.Jpg.Components.Encoder +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// - /// The Huffman encoding specifications. + /// The Huffman encoding specifications. /// internal struct HuffmanSpec { - /// - /// Gets count[i] - The number of codes of length i bits. - /// - public readonly byte[] Count; - - /// - /// Gets value[i] - The decoded value of the codeword at the given index. - /// - public readonly byte[] Values; - - /// - /// Initializes a new instance of the struct. - /// - /// The number of codes. - /// The decoded values. - public HuffmanSpec(byte[] count, byte[] values) - { - this.Count = count; - this.Values = values; - } - #pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines + /// - /// The Huffman encoding specifications. - /// This encoder uses the same Huffman encoding for all images. + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. /// public static readonly HuffmanSpec[] TheHuffmanSpecs = { @@ -37,76 +21,116 @@ new HuffmanSpec( new byte[] { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0 }, new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), new HuffmanSpec( new byte[] { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, + 0, 1, 125 }, new byte[] { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, - 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, - 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, - 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, - 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, - 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, - 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, - 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, - 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, - 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, + 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, + 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, + 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }), new HuffmanSpec( new byte[] { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0 }, new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), // Chrominance AC. new HuffmanSpec( new byte[] { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, + 1, 2, 119 }, new byte[] { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, - 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, - 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, - 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, - 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, - 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, - 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, - 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, - 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, - 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, + 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, + 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, + 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }) }; #pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; + + /// + /// Gets value[i] - The decoded value of the codeword at the given index. + /// + public readonly byte[] Values; + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The number of codes. + /// + /// + /// The decoded values. + /// + public HuffmanSpec(byte[] count, byte[] values) + { + this.Count = count; + this.Values = values; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs index 192e29f08..5efd1a321 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs @@ -1,17 +1,21 @@ -namespace ImageSharp.Formats.Jpg.Components.Encoder +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats.Jpg.Components.Encoder { /// - /// Enumerates the quantization tables + /// Enumerates the quantization tables /// internal enum QuantIndex { /// - /// The luminance quantization table index + /// The luminance quantization table index /// Luminance = 0, /// - /// The chrominance quantization table index + /// The chrominance quantization table index /// Chrominance = 1, } diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 06e722024..42b9d7814 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -10,22 +10,21 @@ namespace ImageSharp.Formats using System.Numerics; using System.Runtime.CompilerServices; - using ImageSharp.Formats.Jpg.Components; using ImageSharp.Formats.Jpg.Components.Encoder; using ImageSharp.Formats.Jpg.Utils; /// - /// Image encoder for writing an image to a stream as a jpeg. + /// Image encoder for writing an image to a stream as a jpeg. /// internal unsafe class JpegEncoderCore { /// - /// The number of quantization tables. + /// The number of quantization tables. /// private const int QuantizationTableCount = 2; /// - /// Counts the number of bits needed to hold an integer. + /// Counts the number of bits needed to hold an integer. /// private static readonly uint[] BitCountLut = { @@ -45,15 +44,15 @@ namespace ImageSharp.Formats }; /// - /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: - /// - the marker length "\x00\x0c", - /// - the number of components "\x03", - /// - component 1 uses DC table 0 and AC table 0 "\x01\x00", - /// - component 2 uses DC table 1 and AC table 1 "\x02\x11", - /// - component 3 uses DC table 1 and AC table 1 "\x03\x11", - /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - /// should be 0x00, 0x3f, 0x00<<4 | 0x00. + /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: + /// - the marker length "\x00\x0c", + /// - the number of components "\x03", + /// - component 1 uses DC table 0 and AC table 0 "\x01\x00", + /// - component 2 uses DC table 1 and AC table 1 "\x02\x11", + /// - component 3 uses DC table 1 and AC table 1 "\x03\x11", + /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + /// should be 0x00, 0x3f, 0x00<<4 | 0x00. /// private static readonly byte[] SosHeaderYCbCr = { @@ -78,10 +77,10 @@ namespace ImageSharp.Formats }; /// - /// The unscaled quantization tables in zig-zag order. Each - /// encoder copies and scales the tables according to its quality parameter. - /// The values are derived from section K.1 after converting from natural to - /// zig-zag order. + /// The unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. /// private static readonly byte[,] UnscaledQuant = { @@ -104,58 +103,69 @@ namespace ImageSharp.Formats }; /// - /// A scratch buffer to reduce allocations. + /// A scratch buffer to reduce allocations. /// private readonly byte[] buffer = new byte[16]; /// - /// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. + /// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. /// private readonly byte[] emitBuffer = new byte[64]; /// - /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + identifier. + /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + + /// identifier. /// private readonly byte[] huffmanBuffer = new byte[179]; /// - /// The accumulated bits to write to the stream. + /// The accumulated bits to write to the stream. /// private uint accumulatedBits; /// - /// The accumulated bit count. + /// The accumulated bit count. /// private uint bitCount; /// - /// The scaled chrominance table, in zig-zag order. + /// The scaled chrominance table, in zig-zag order. /// private Block8x8F chrominanceQuantTable; /// - /// The scaled luminance table, in zig-zag order. + /// The scaled luminance table, in zig-zag order. /// private Block8x8F luminanceQuantTable; /// - /// The output stream. All attempted writes after the first error become no-ops. + /// The output stream. All attempted writes after the first error become no-ops. /// private Stream outputStream; /// - /// The subsampling method to use. + /// The subsampling method to use. /// private JpegSubsample subsample; /// /// Encode writes the image to the jpeg baseline format with the given options. /// - /// The pixel format. - /// The image to write from. - /// The stream to write to. - /// The quality. - /// The subsampling mode. + /// + /// The pixel format. + /// + /// + /// The image to write from. + /// + /// + /// The stream to write to. + /// + /// + /// The quality. + /// + /// + /// The subsampling mode. + /// public void Encode(Image image, Stream stream, int quality, JpegSubsample sample) where TColor : struct, IPackedPixel, IEquatable { @@ -249,14 +259,30 @@ namespace ImageSharp.Formats /// /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values. /// - /// The pixel format. - /// The pixel accessor. - /// The x-position within the image. - /// The y-position within the image. - /// The luminance block. - /// The red chroma block. - /// The blue chroma block. - /// Temporal provided by the caller + /// + /// The pixel format. + /// + /// + /// The pixel accessor. + /// + /// + /// The x-position within the image. + /// + /// + /// The y-position within the image. + /// + /// + /// The luminance block. + /// + /// + /// The red chroma block. + /// + /// + /// The blue chroma block. + /// + /// + /// Temporal provided by the caller + /// private static void ToYCbCr( PixelAccessor pixels, int x, @@ -264,7 +290,8 @@ namespace ImageSharp.Formats Block8x8F* yBlock, Block8x8F* cbBlock, Block8x8F* crBlock, - PixelArea rgbBytes) where TColor : struct, IPackedPixel, IEquatable + PixelArea rgbBytes) + where TColor : struct, IPackedPixel, IEquatable { float* yBlockRaw = (float*)yBlock; float* cbBlockRaw = (float*)cbBlock; @@ -300,10 +327,18 @@ namespace ImageSharp.Formats /// /// Emits the least significant count of bits of bits to the bit-stream. - /// The precondition is bits < 1<<nBits && nBits <= 16. + /// The precondition is bits + /// + /// < 1<<nBits && nBits <= 16 + /// + /// . /// - /// The packed bits. - /// The number of bits + /// + /// The packed bits. + /// + /// + /// The number of bits + /// private void Emit(uint bits, uint count) { count += this.bitCount; @@ -341,8 +376,12 @@ namespace ImageSharp.Formats /// /// Emits the given value with the given Huffman encoder. /// - /// The index of the Huffman encoder - /// The value to encode. + /// + /// The index of the Huffman encoder + /// + /// + /// The value to encode. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EmitHuff(HuffIndex index, int value) { @@ -353,9 +392,15 @@ namespace ImageSharp.Formats /// /// Emits a run of runLength copies of value encoded with the given Huffman encoder. /// - /// The index of the Huffman encoder - /// The number of copies to encode. - /// The value to encode. + /// + /// The index of the Huffman encoder + /// + /// + /// The number of copies to encode. + /// + /// + /// The value to encode. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EmitHuffRLE(HuffIndex index, int runLength, int value) { @@ -387,8 +432,12 @@ namespace ImageSharp.Formats /// /// Encodes the image with no subsampling. /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. + /// + /// The pixel format. + /// + /// + /// The pixel accessor providing access to the image pixels. + /// private void Encode444(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { @@ -448,8 +497,12 @@ namespace ImageSharp.Formats /// /// Writes the application header containing the JFIF identifier plus extra data. /// - /// The resolution of the image in the x- direction. - /// The resolution of the image in the y- direction. + /// + /// The resolution of the image in the x- direction. + /// + /// + /// The resolution of the image in the y- direction. + /// private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) { // Write the start of image marker. Markers are always prefixed with with 0xff. @@ -487,17 +540,33 @@ namespace ImageSharp.Formats /// /// Writes a block of pixel data using the given quantization table, - /// returning the post-quantized DC value of the DCT-transformed block. - /// The block is in natural (not zig-zag) order. + /// returning the post-quantized DC value of the DCT-transformed block. + /// The block is in natural (not zig-zag) order. /// - /// The quantization table index. - /// The previous DC value. - /// Source block - /// Temporal block to be used as FDCT Destination - /// Temporal block 2 - /// Quantization table - /// The 8x8 Unzig block ptr - /// The + /// + /// The quantization table index. + /// + /// + /// The previous DC value. + /// + /// + /// Source block + /// + /// + /// Temporal block to be used as FDCT Destination + /// + /// + /// Temporal block 2 + /// + /// + /// Quantization table + /// + /// + /// The 8x8 Unzig block ptr + /// + /// + /// The + /// private float WriteBlock( QuantIndex index, float prevDC, @@ -554,7 +623,9 @@ namespace ImageSharp.Formats /// /// Writes the Define Huffman Table marker and tables. /// - /// The number of components to write. + /// + /// The number of components to write. + /// private void WriteDefineHuffmanTables(int componentCount) { // Table identifiers. @@ -601,7 +672,7 @@ namespace ImageSharp.Formats } /// - /// Writes the Define Quantization Marker and tables. + /// Writes the Define Quantization Marker and tables. /// private void WriteDefineQuantizationTables() { @@ -625,7 +696,9 @@ namespace ImageSharp.Formats /// /// Writes the EXIF profile. /// - /// The exif profile. + /// + /// The exif profile. + /// /// /// Thrown if the EXIF profile size exceeds the limit /// @@ -657,9 +730,14 @@ namespace ImageSharp.Formats /// /// Writes the metadata profiles to the image. /// - /// The image. - /// The pixel format. - private void WriteProfiles(Image image) where TColor : struct, IPackedPixel, IEquatable + /// + /// The image. + /// + /// + /// The pixel format. + /// + private void WriteProfiles(Image image) + where TColor : struct, IPackedPixel, IEquatable { this.WriteProfile(image.ExifProfile); } @@ -667,9 +745,15 @@ namespace ImageSharp.Formats /// /// Writes the Start Of Frame (Baseline) marker /// - /// The width of the image - /// The height of the image - /// The number of components in a pixel + /// + /// The width of the image + /// + /// + /// The height of the image + /// + /// + /// The number of components in a pixel + /// private void WriteStartOfFrame(int width, int height, int componentCount) { // "default" to 4:2:0 @@ -723,7 +807,9 @@ namespace ImageSharp.Formats /// /// Writes the StartOfScan marker. /// - /// The pixel format. + /// + /// The pixel format. + /// /// /// The pixel accessor providing access to the image pixels. /// @@ -751,8 +837,9 @@ namespace ImageSharp.Formats #pragma warning disable SA1201 // MethodShouldNotFollowAStruct /// - /// Poor man's stackalloc for Encode420. - /// This struct belongs to Encode420. Much easeier to understand code if they are close to each other. Why should I move it Up? :P + /// Poor man's stackalloc for Encode420. + /// This struct belongs to Encode420. Much easeier to understand code if they are close to each other. Why should I + /// move it Up? :P /// private struct BlockQuad { @@ -761,10 +848,14 @@ namespace ImageSharp.Formats /// /// Encodes the image with subsampling. The Cb and Cr components are each subsampled - /// at a factor of 2 both horizontally and vertically. + /// at a factor of 2 both horizontally and vertically. /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. + /// + /// The pixel format. + /// + /// + /// The pixel accessor providing access to the image pixels. + /// private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { @@ -838,8 +929,12 @@ namespace ImageSharp.Formats /// /// Writes the header for a marker with the given length. /// - /// The marker to write. - /// The marker length. + /// + /// The marker to write. + /// + /// + /// The marker length. + /// private void WriteMarkerHeader(byte marker, int length) { // Markers are always prefixed with with 0xff. diff --git a/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs index f4c2fa2c4..3a87723bc 100644 --- a/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs +++ b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs @@ -10,18 +10,29 @@ namespace ImageSharp.Formats.Jpg.Utils using ImageSharp.Formats.Jpg.Components.Encoder; /// - /// Jpeg specific utilities and extension methods + /// Jpeg specific utilities and extension methods /// internal static unsafe class JpegUtils { /// - /// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of the image. + /// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of + /// the image. /// - /// The pixel type - /// The input pixel acessor - /// The destination - /// Starting Y coord - /// Starting X coord + /// + /// The pixel type + /// + /// + /// The input pixel acessor + /// + /// + /// The destination + /// + /// + /// Starting Y coord + /// + /// + /// Starting X coord + /// public static void CopyRGBBytesStretchedTo( this PixelAccessor pixels, PixelArea dest, @@ -38,8 +49,12 @@ namespace ImageSharp.Formats.Jpg.Utils /// /// Copy an RGB value /// - /// Source pointer - /// Destination pointer + /// + /// Source pointer + /// + /// + /// Destination pointer + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void CopyRgb(byte* source, byte* dest) { @@ -48,6 +63,37 @@ namespace ImageSharp.Formats.Jpg.Utils *dest = *source; // B } + /// + /// Writes data to "Define Quantization Tables" block for QuantIndex + /// + /// + /// The "Define Quantization Tables" block + /// + /// + /// Offset in dqt + /// + /// + /// The quantization index + /// + /// + /// The quantazation table to copy data from + /// + internal static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) + { + dqt[offset++] = (byte)i; + for (int j = 0; j < Block8x8F.ScalarCount; j++) + { + dqt[offset++] = (byte)q[j]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsInvalidStretchArea(PixelArea area, int fromX, int fromY) + where TColor : struct, IPackedPixel, IEquatable + { + return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; + } + private static void StretchPixels(PixelArea area, int fromX, int fromY) where TColor : struct, IPackedPixel, IEquatable { @@ -84,21 +130,5 @@ namespace ImageSharp.Formats.Jpg.Utils } } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsInvalidStretchArea(PixelArea area, int fromX, int fromY) - where TColor : struct, IPackedPixel, IEquatable - { - return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; - } - - internal static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) - { - dqt[offset++] = (byte)i; - for (int j = 0; j < Block8x8F.ScalarCount; j++) - { - dqt[offset++] = (byte)q[j]; - } - } } } \ No newline at end of file