From 09be16fe8d1be0b1d9282add146cd24fcb4ce3a4 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Mon, 26 Dec 2016 03:08:39 +0100 Subject: [PATCH 01/24] re-added tests46 --- ImageSharp.sln | 7 + global.json | 2 +- tests/ImageSharp.Tests46/HelloTest.cs | 29 +++ .../ImageSharp.Tests46.csproj | 179 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++ tests/ImageSharp.Tests46/TestFile.cs | 73 +++++++ tests/ImageSharp.Tests46/packages.config | 10 + 7 files changed, 335 insertions(+), 1 deletion(-) 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/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..d53ee3d9c --- /dev/null +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -0,0 +1,179 @@ + + + + + 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\ReferenceImplementationsTests.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 2822e2a9506eeac39b2f8d3e4b29d9ff596a683b Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Mon, 26 Dec 2016 04:14:10 +0100 Subject: [PATCH 02/24] Benchmark_JpegDecoder --- .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 32 +++++++++++++++---- .../Formats/Jpg/ReferenceImplementations.cs | 2 +- .../Formats/Jpg/UtilityTestClassBase.cs | 2 +- tests/ImageSharp.Tests/TestFile.cs | 7 +++- tests/ImageSharp.Tests/TestImages.cs | 2 +- tests/ImageSharp.Tests46/TestFile.cs | 7 +++- 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 70ddb3000..3bf7ed92b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -13,13 +13,11 @@ namespace ImageSharp.Tests using ImageSharp.Formats.Jpg; - public class JpegTests + public class JpegTests : MeasureFixture { - private ITestOutputHelper Output { get; } - public JpegTests(ITestOutputHelper output) + : base(output) { - this.Output = output; } public static IEnumerable AllJpegFiles => TestImages.Jpeg.All; @@ -62,7 +60,7 @@ namespace ImageSharp.Tests private const int BenchmarkExecTimes = 30; - public static readonly string[] BenchmarkFiles = + public static readonly string[] EncoderBenchmarkFiles = { TestImages.Bmp.Car, TestImages.Bmp.NegHeight, TestImages.Bmp.F, TestImages.Png.Splash, @@ -72,11 +70,31 @@ namespace ImageSharp.Tests private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; //PixelTypes.Color | PixelTypes.Argb; + [Theory] // Benchmark, enable manually + [InlineData(TestImages.Jpeg.Cmyk)] + [InlineData(TestImages.Jpeg.Ycck)] + [InlineData(TestImages.Jpeg.Calliphora)] + [InlineData(TestImages.Jpeg.Jpeg400)] + [InlineData(TestImages.Jpeg.Jpeg420)] + [InlineData(TestImages.Jpeg.Jpeg444)] + public void Benchmark_JpegDecoder(string fileName) + { + string path = TestFile.GetPath(fileName); + byte[] bytes = File.ReadAllBytes(path); + this.Measure( + 100, + () => + { + Image img = new Image(bytes); + }, + $"Decode {fileName}"); + + } //[Theory] // Benchmark, enable manually - [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)] - [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio444, 75)] + [WithFileCollection(nameof(EncoderBenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)] + [WithFileCollection(nameof(EncoderBenchmarkFiles), 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/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index c045d8937..56ca0d0a7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Tests using ImageSharp.Formats; using ImageSharp.Formats.Jpg; - + /// /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd diff --git a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs index 74c6772b7..650b9fc07 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Tests /// /// Utility class to measure the execution of an operation. /// - public class MeasureFixture + public class MeasureFixture : TestBase { protected bool EnablePrinting = true; diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index aab7c2620..892e344d6 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -34,12 +34,17 @@ namespace ImageSharp.Tests this.Bytes = File.ReadAllBytes(file); this.image = new Image(this.Bytes); } + + public static string GetPath(string file) + { + return Path.Combine(FormatsDirectory, file); + } public static TestFile Create(string file) { return cache.GetOrAdd(file, (string fileName) => { - return new TestFile(FormatsDirectory + fileName); + return new TestFile(GetPath(file)); }); } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e5b46b0e1..7265e45a4 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -56,7 +56,7 @@ namespace ImageSharp.Tests public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; public static readonly string[] All = { - Cmyk, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray, + Cmyk, Ycck, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray, Festzug, Hiyamugi, Jpeg400, Jpeg420, Jpeg444, }; diff --git a/tests/ImageSharp.Tests46/TestFile.cs b/tests/ImageSharp.Tests46/TestFile.cs index 95e2fc083..1b9842857 100644 --- a/tests/ImageSharp.Tests46/TestFile.cs +++ b/tests/ImageSharp.Tests46/TestFile.cs @@ -28,7 +28,12 @@ namespace ImageSharp.Tests this.Bytes = File.ReadAllBytes(file); this.image = new Image(this.Bytes); } - + + public static string GetPath(string file) + { + return Path.Combine(FormatsDirectory, file); + } + public static TestFile Create(string file) { return cache.GetOrAdd(file, (string fileName) => From 27713664ba5ff5b51680d3562c444495365683aa Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Mon, 26 Dec 2016 06:35:04 +0100 Subject: [PATCH 03/24] refactored GrayImage class --to--> reusable JpegPixelArea cleaned some mess in JpegDecoderCore --- .../Jpg/Components/Decoder/GrayImage.cs | 101 --------------- .../Jpg/Components/Decoder/JpegPixelArea.cs | 116 ++++++++++++++++++ .../Jpg/Components/Decoder/YCbCrImage.cs | 98 +++++++-------- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 113 +++++++---------- .../Formats/Jpg/Utils/ArrayPoolManager.cs | 13 ++ .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 2 +- 6 files changed, 222 insertions(+), 221 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs create mode 100644 src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs create mode 100644 src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs deleted file mode 100644 index caa30e62d..000000000 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats.Jpg -{ - /// - /// Represents a grayscale image - /// - internal class GrayImage - { - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - public GrayImage(int width, int height) - { - this.Width = width; - this.Height = height; - this.Pixels = new byte[width * height]; - this.Stride = width; - this.Offset = 0; - } - - /// - /// Prevents a default instance of the class from being created. - /// - private GrayImage() - { - } - - /// - /// Gets or sets the pixels. - /// - public byte[] Pixels { get; set; } - - /// - /// Gets or sets the stride. - /// - public int Stride { get; set; } - - /// - /// Gets or sets the horizontal position. - /// - public int X { get; set; } - - /// - /// Gets or sets the vertical position. - /// - public int Y { get; set; } - - /// - /// Gets or sets the width. - /// - public int Width { get; set; } - - /// - /// Gets or sets the height. - /// - public int Height { get; set; } - - /// - /// Gets or sets the offset - /// - public int Offset { get; set; } - - /// - /// Gets an image made up of a subset of the originals pixels. - /// - /// The x-coordinate of the image. - /// The y-coordinate of the image. - /// The width. - /// The height. - /// - /// The . - /// - public GrayImage Subimage(int x, int y, int width, int height) - { - return new GrayImage - { - Width = width, - Height = height, - Pixels = this.Pixels, - Stride = this.Stride, - Offset = (y * this.Stride) + x - }; - } - - /// - /// Gets the row offset at the given position - /// - /// The y-coordinate of the image. - /// The - public int GetRowOffset(int y) - { - return this.Offset + (y * this.Stride); - } - } -} diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs new file mode 100644 index 000000000..c4168dd1f --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs @@ -0,0 +1,116 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + + using System; + using System.Buffers; + using System.Runtime.CompilerServices; + + /// + /// Represents a grayscale image + /// + internal struct JpegPixelArea + { + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + public static JpegPixelArea CreatePooled(int width, int height) + { + int size = width * height; + //var pixels = ArrayPool.Shared.Rent(size); + //Array.Clear(pixels, 0, size); + var pixels = ArrayPoolManager.RentCleanArray(size); + return new JpegPixelArea(pixels, width, 0); + } + + public JpegPixelArea(byte[] pixels, int widthOrStride, int offset) + { + this.Stride = widthOrStride; + this.Pixels = pixels; + this.Offset = offset; + } + + public void ReturnPooled() + { + if (this.Pixels == null) return; + ArrayPoolManager.ReturnArray(this.Pixels); + this.Pixels = null; + } + + /// + /// Gets or sets the pixels. + /// + public byte[] Pixels { get; private set; } + + public bool Created => this.Pixels != null; + + /// + /// Gets or sets the width. + /// + public int Stride { get; private set; } + + /// + /// Gets or sets the offset + /// + public int Offset { get; private set; } + + /// + /// Gets an image made up of a subset of the originals pixels. + /// + /// The x-coordinate of the image. + /// The y-coordinate of the image. + /// The width. + /// The height. + /// + /// The . + /// + public JpegPixelArea Subimage(int x, int y, int width, int height) + { + return new JpegPixelArea + { + Stride = width, + Pixels = this.Pixels, + Offset = (y * this.Stride) + x + }; + } + + /// + /// Get the subarea that belongs to the given block indices + /// + /// The block X index + /// The block Y index + /// + public JpegPixelArea GetOffsetedAreaForBlock(int bx, int by) + { + int offset = this.Offset + 8 * (by * this.Stride + bx); + return new JpegPixelArea(this.Pixels, this.Stride, offset); + } + + public byte this[int x, int y] => this.Pixels[y * this.Stride + x]; + + /// + /// Gets the row offset at the given position + /// + /// The y-coordinate of the image. + /// The + public int GetRowOffset(int y) + { + return this.Offset + (y * this.Stride); + } + + public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp) + { + // Level shift by +128, clip to [0, 255], and write to dst. + block->CopyColorsTo(new MutableSpan(this.Pixels, this.Offset), this.Stride, temp); + } + } +} diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs index cba9c4461..6ae1d3540 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats.Jpg { + using System.Buffers; + /// /// Represents an image made up of three color components (luminance, blue chroma, red chroma) /// @@ -20,25 +22,29 @@ namespace ImageSharp.Formats.Jpg { int cw, ch; YCbCrSize(width, height, ratio, out cw, out ch); - this.YChannel = new byte[width * height]; - this.CbChannel = new byte[cw * ch]; - this.CrChannel = new byte[cw * ch]; + this.YPixels = new byte[width * height]; + this.CbPixels = new byte[cw * ch]; + this.CrPixels = new byte[cw * ch]; this.Ratio = ratio; this.YStride = width; this.CStride = cw; this.X = 0; this.Y = 0; - this.Width = width; - this.Height = height; } + public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset); + + public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset); + + public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset); + /// /// Prevents a default instance of the class from being created. /// private YCbCrImage() { } - + /// /// Provides enumeration of the various available subsample ratios. /// @@ -78,38 +84,38 @@ namespace ImageSharp.Formats.Jpg /// /// Gets or sets the luminance components channel. /// - public byte[] YChannel { get; set; } + public byte[] YPixels { get; private set; } /// /// Gets or sets the blue chroma components channel. /// - public byte[] CbChannel { get; set; } + public byte[] CbPixels { get; private set; } /// /// Gets or sets the red chroma components channel. /// - public byte[] CrChannel { get; set; } + public byte[] CrPixels { get; private set; } /// /// Gets or sets the Y slice index delta between vertically adjacent pixels. /// - public int YStride { get; set; } + public int YStride { get; private set; } /// /// Gets or sets the red and blue chroma slice index delta between vertically adjacent pixels /// that map to separate chroma samples. /// - public int CStride { get; set; } + public int CStride { get; private set; } /// /// Gets or sets the index of the first luminance element. /// - public int YOffset { get; set; } + public int YOffset { get; private set; } /// /// Gets or sets the index of the first element of red or blue chroma. /// - public int COffset { get; set; } + public int COffset { get; private set; } /// /// Gets or sets the horizontal position. @@ -120,50 +126,38 @@ namespace ImageSharp.Formats.Jpg /// Gets or sets the vertical position. /// public int Y { get; set; } - - /// - /// Gets or sets the width. - /// - public int Width { get; set; } - - /// - /// Gets or sets the height. - /// - public int Height { get; set; } - + /// /// Gets or sets the subsampling ratio. /// public YCbCrSubsampleRatio Ratio { get; set; } - /// - /// Gets an image made up of a subset of the originals pixels. - /// - /// The x-coordinate of the image. - /// The y-coordinate of the image. - /// The width. - /// The height. - /// - /// The . - /// - public YCbCrImage Subimage(int x, int y, int width, int height) - { - YCbCrImage ret = new YCbCrImage - { - Width = width, - Height = height, - YChannel = this.YChannel, - CbChannel = this.CbChannel, - CrChannel = this.CrChannel, - Ratio = this.Ratio, - YStride = this.YStride, - CStride = this.CStride, - YOffset = (y * this.YStride) + x, - COffset = (y * this.CStride) + x - }; - return ret; - } - + ///// + ///// Gets an image made up of a subset of the originals pixels. + ///// + ///// The x-coordinate of the image. + ///// The y-coordinate of the image. + ///// The width. + ///// The height. + ///// + ///// The . + ///// + //public YCbCrImage Subimage(int x, int y, int width, int height) + //{ + // YCbCrImage ret = new YCbCrImage + // { + // YPixels = this.YPixels, + // CbPixels = this.CbPixels, + // CrPixels = this.CrPixels, + // Ratio = this.Ratio, + // YStride = this.YStride, + // CStride = this.CStride, + // YOffset = (y * this.YStride) + x, + // COffset = (y * this.CStride) + x + // }; + // return ret; + //} + /// /// Returns the offset of the first luminance component at the given row /// diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 761ad891e..2b53ebaaf 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -122,22 +122,15 @@ namespace ImageSharp.Formats /// /// A grayscale image to decode to. /// - private GrayImage grayImage; + private JpegPixelArea grayImage; /// /// The full color image to decode to. /// private YCbCrImage ycbcrImage; - /// - /// The array of keyline pixels in a CMYK image - /// - private byte[] blackPixels; - - /// - /// The width in bytes or a single row of keyline pixels in a CMYK image - /// - private int blackStride; + + private JpegPixelArea blackImage; /// /// The restart interval @@ -420,7 +413,7 @@ namespace ImageSharp.Formats } } - if (this.grayImage != null) + if (this.grayImage.Created) { this.ConvertFromGrayScale(this.imageWidth, this.imageHeight, image); } @@ -480,6 +473,8 @@ namespace ImageSharp.Formats } this.bytes.Dispose(); + this.grayImage.ReturnPooled(); + this.blackImage.ReturnPooled(); } /// @@ -1228,9 +1223,9 @@ namespace ImageSharp.Formats for (int x = 0; x < width; x++) { - byte yy = this.ycbcrImage.YChannel[yo + x]; - byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; - byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; + byte yy = this.ycbcrImage.YPixels[yo + x]; + byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; TColor packed = default(TColor); this.PackYcck(ref packed, yy, cb, cr, x, y); @@ -1268,9 +1263,9 @@ namespace ImageSharp.Formats for (int x = 0; x < width; x++) { - byte cyan = this.ycbcrImage.YChannel[yo + x]; - byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)]; - byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)]; + byte cyan = this.ycbcrImage.YPixels[yo + x]; + byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)]; TColor packed = default(TColor); this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); @@ -1343,9 +1338,9 @@ namespace ImageSharp.Formats for (int x = 0; x < width; x++) { - byte yy = this.ycbcrImage.YChannel[yo + x]; - byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; - byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; + byte yy = this.ycbcrImage.YPixels[yo + x]; + byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; TColor packed = default(TColor); PackYcbCr(ref packed, yy, cb, cr); @@ -1383,9 +1378,9 @@ namespace ImageSharp.Formats for (int x = 0; x < width; x++) { - byte red = this.ycbcrImage.YChannel[yo + x]; - byte green = this.ycbcrImage.CbChannel[co + (x / scale)]; - byte blue = this.ycbcrImage.CrChannel[co + (x / scale)]; + byte red = this.ycbcrImage.YPixels[yo + x]; + byte green = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte blue = this.ycbcrImage.CrPixels[co + (x / scale)]; TColor packed = default(TColor); packed.PackFromBytes(red, green, blue, 255); @@ -1506,11 +1501,8 @@ namespace ImageSharp.Formats int v0 = this.componentArray[0].VerticalFactor; int mxx = (this.imageWidth + (8 * h0) - 1) / (8 * h0); int myy = (this.imageHeight + (8 * v0) - 1) / (8 * v0); - - if (this.grayImage == null && this.ycbcrImage == null) - { - this.MakeImage(mxx, myy); - } + + this.MakeImage(mxx, myy); if (this.isProgressive) { @@ -1540,6 +1532,8 @@ namespace ImageSharp.Formats // blocks: the third block in the first row has (bx, by) = (2, 0). int bx, by, blockCount = 0; + // TODO: A DecoderScanProcessor struct could clean up this mess + Block8x8F b = default(Block8x8F); Block8x8F temp1 = default(Block8x8F); Block8x8F temp2 = default(Block8x8F); @@ -1601,7 +1595,8 @@ namespace ImageSharp.Formats int qtIndex = this.componentArray[compIndex].Selector; - // TODO: Find a way to clean up this mess + // TODO: A DecoderScanProcessor struct could clean up this mess + // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. (The first one could be async) fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex]) { // Load the previous partially decoded coefficients, if applicable. @@ -1812,52 +1807,33 @@ namespace ImageSharp.Formats DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2); - byte[] dst; - int offset; - int stride; + var destChannel = this.GetDestinationChannel(compIndex); + var destArea = destChannel.GetOffsetedAreaForBlock(bx, by); + destArea.LoadColorsFrom(temp1, temp2); + } + private JpegPixelArea GetDestinationChannel(int compIndex) + { if (this.componentCount == 1) { - dst = this.grayImage.Pixels; - stride = this.grayImage.Stride; - offset = this.grayImage.Offset + (8 * ((@by * this.grayImage.Stride) + bx)); + return this.grayImage; } else { switch (compIndex) { case 0: - dst = this.ycbcrImage.YChannel; - stride = this.ycbcrImage.YStride; - offset = this.ycbcrImage.YOffset + (8 * ((@by * this.ycbcrImage.YStride) + bx)); - break; - + return this.ycbcrImage.YChannel; case 1: - dst = this.ycbcrImage.CbChannel; - stride = this.ycbcrImage.CStride; - offset = this.ycbcrImage.COffset + (8 * ((@by * this.ycbcrImage.CStride) + bx)); - break; - + return this.ycbcrImage.CbChannel; case 2: - dst = this.ycbcrImage.CrChannel; - stride = this.ycbcrImage.CStride; - offset = this.ycbcrImage.COffset + (8 * ((@by * this.ycbcrImage.CStride) + bx)); - break; - + return this.ycbcrImage.CrChannel; case 3: - - dst = this.blackPixels; - stride = this.blackStride; - offset = 8 * ((@by * this.blackStride) + bx); - break; - + return this.blackImage; default: throw new ImageFormatException("Too many components"); } } - - // Level shift by +128, clip to [0, 255], and write to dst. - temp1->CopyColorsTo(new MutableSpan(dst, offset), stride, temp2); } private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv) @@ -2071,7 +2047,7 @@ namespace ImageSharp.Formats return zig; } - + /// /// Makes the image from the buffer. /// @@ -2079,9 +2055,11 @@ namespace ImageSharp.Formats /// The vertical MCU count private void MakeImage(int mxx, int myy) { + if (this.grayImage.Created || this.ycbcrImage != null) return; + if (this.componentCount == 1) { - GrayImage gray = new GrayImage(8 * mxx, 8 * myy); + JpegPixelArea gray = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy); this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight); } else @@ -2114,15 +2092,16 @@ namespace ImageSharp.Formats break; } - YCbCrImage ycbcr = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio); - this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight); + this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio); + //this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight); if (this.componentCount == 4) { int h3 = this.componentArray[3].HorizontalFactor; int v3 = this.componentArray[3].VerticalFactor; - this.blackPixels = new byte[8 * h3 * mxx * 8 * v3 * myy]; - this.blackStride = 8 * h3 * mxx; + + this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * mxx, 8 * v3 * myy); + } } } @@ -2179,7 +2158,7 @@ namespace ImageSharp.Formats float yellow = (y + (1.772F * ccb)).Clamp(0, 255) / 255F; // Get keyline - float keyline = (255 - this.blackPixels[(yy * this.blackStride) + xx]) / 255F; + float keyline = (255 - this.blackImage[xx, yy]) / 255F; // Convert back to RGB byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255); @@ -2204,7 +2183,7 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel, IEquatable { // Get keyline - float keyline = (255 - this.blackPixels[(yy * this.blackStride) + xx]) / 255F; + float keyline = (255 - this.blackImage[xx, yy]) / 255F; // Convert back to RGB. CMY are not inverted byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255); diff --git a/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs b/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs new file mode 100644 index 000000000..cb3cd0fe5 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs @@ -0,0 +1,13 @@ +namespace ImageSharp.Formats +{ + using System.Buffers; + + internal class ArrayPoolManager + { + private static readonly ArrayPool Pool = ArrayPool.Create(); + + public static T[] RentCleanArray(int minimumLength) => Pool.Rent(minimumLength); + + public static void ReturnArray(T[] array) => Pool.Return(array, true); + } +} \ 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 3bf7ed92b..662ec9728 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -70,7 +70,7 @@ namespace ImageSharp.Tests private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; //PixelTypes.Color | PixelTypes.Argb; - [Theory] // Benchmark, enable manually + //[Theory] // Benchmark, enable manually [InlineData(TestImages.Jpeg.Cmyk)] [InlineData(TestImages.Jpeg.Ycck)] [InlineData(TestImages.Jpeg.Calliphora)] From ee0372463944319334b8ca5a21cb9d2e00562571 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Mon, 26 Dec 2016 06:44:22 +0100 Subject: [PATCH 04/24] pooling YCbCrImage contents --- .../Jpg/Components/Decoder/JpegPixelArea.cs | 26 +------- .../Jpg/Components/Decoder/YCbCrImage.cs | 64 ++++++++----------- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 10 +-- .../{ArrayPoolManager.cs => CleanPooler.cs} | 2 +- 4 files changed, 34 insertions(+), 68 deletions(-) rename src/ImageSharp/Formats/Jpg/Utils/{ArrayPoolManager.cs => CleanPooler.cs} (88%) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs index c4168dd1f..0161542d8 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Formats.Jpg int size = width * height; //var pixels = ArrayPool.Shared.Rent(size); //Array.Clear(pixels, 0, size); - var pixels = ArrayPoolManager.RentCleanArray(size); + var pixels = CleanPooler.RentCleanArray(size); return new JpegPixelArea(pixels, width, 0); } @@ -39,7 +39,7 @@ namespace ImageSharp.Formats.Jpg public void ReturnPooled() { if (this.Pixels == null) return; - ArrayPoolManager.ReturnArray(this.Pixels); + CleanPooler.ReturnArray(this.Pixels); this.Pixels = null; } @@ -59,27 +59,7 @@ namespace ImageSharp.Formats.Jpg /// Gets or sets the offset /// public int Offset { get; private set; } - - /// - /// Gets an image made up of a subset of the originals pixels. - /// - /// The x-coordinate of the image. - /// The y-coordinate of the image. - /// The width. - /// The height. - /// - /// The . - /// - public JpegPixelArea Subimage(int x, int y, int width, int height) - { - return new JpegPixelArea - { - Stride = width, - Pixels = this.Pixels, - Offset = (y * this.Stride) + x - }; - } - + /// /// Get the subarea that belongs to the given block indices /// diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs index 6ae1d3540..bd0d58dd2 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs @@ -5,12 +5,13 @@ namespace ImageSharp.Formats.Jpg { + using System; using System.Buffers; /// /// Represents an image made up of three color components (luminance, blue chroma, red chroma) /// - internal class YCbCrImage + internal class YCbCrImage : IDisposable { /// /// Initializes a new instance of the class. @@ -18,14 +19,16 @@ namespace ImageSharp.Formats.Jpg /// The width. /// The height. /// The ratio. - public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio) + public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio, int yOffset, int cOffset) { int cw, ch; YCbCrSize(width, height, ratio, out cw, out ch); - this.YPixels = new byte[width * height]; - this.CbPixels = new byte[cw * ch]; - this.CrPixels = new byte[cw * ch]; + this.YPixels = CleanPooler.RentCleanArray(width * height); + this.CbPixels = CleanPooler.RentCleanArray(cw * ch); + this.CrPixels = CleanPooler.RentCleanArray(cw * ch); this.Ratio = ratio; + this.YOffset = yOffset; + this.COffset = cOffset; this.YStride = width; this.CStride = cw; this.X = 0; @@ -41,8 +44,10 @@ namespace ImageSharp.Formats.Jpg /// /// Prevents a default instance of the class from being created. /// - private YCbCrImage() + private YCbCrImage(int yOffset, int cOffset) { + this.YOffset = yOffset; + this.COffset = cOffset; } /// @@ -84,38 +89,38 @@ namespace ImageSharp.Formats.Jpg /// /// Gets or sets the luminance components channel. /// - public byte[] YPixels { get; private set; } + public byte[] YPixels { get; } /// /// Gets or sets the blue chroma components channel. /// - public byte[] CbPixels { get; private set; } + public byte[] CbPixels { get; } /// /// Gets or sets the red chroma components channel. /// - public byte[] CrPixels { get; private set; } + public byte[] CrPixels { get; } /// /// Gets or sets the Y slice index delta between vertically adjacent pixels. /// - public int YStride { get; private set; } + public int YStride { get; } /// /// Gets or sets the red and blue chroma slice index delta between vertically adjacent pixels /// that map to separate chroma samples. /// - public int CStride { get; private set; } + public int CStride { get; } /// /// Gets or sets the index of the first luminance element. /// - public int YOffset { get; private set; } + public int YOffset { get; } /// /// Gets or sets the index of the first element of red or blue chroma. /// - public int COffset { get; private set; } + public int COffset { get; } /// /// Gets or sets the horizontal position. @@ -132,32 +137,6 @@ namespace ImageSharp.Formats.Jpg /// public YCbCrSubsampleRatio Ratio { get; set; } - ///// - ///// Gets an image made up of a subset of the originals pixels. - ///// - ///// The x-coordinate of the image. - ///// The y-coordinate of the image. - ///// The width. - ///// The height. - ///// - ///// The . - ///// - //public YCbCrImage Subimage(int x, int y, int width, int height) - //{ - // YCbCrImage ret = new YCbCrImage - // { - // YPixels = this.YPixels, - // CbPixels = this.CbPixels, - // CrPixels = this.CrPixels, - // Ratio = this.Ratio, - // YStride = this.YStride, - // CStride = this.CStride, - // YOffset = (y * this.YStride) + x, - // COffset = (y * this.CStride) + x - // }; - // return ret; - //} - /// /// Returns the offset of the first luminance component at the given row /// @@ -236,5 +215,12 @@ namespace ImageSharp.Formats.Jpg break; } } + + public void Dispose() + { + CleanPooler.ReturnArray(this.YPixels); + CleanPooler.ReturnArray(this.CrPixels); + CleanPooler.ReturnArray(this.CbPixels); + } } } diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 2b53ebaaf..08fcb1018 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -472,6 +472,7 @@ namespace ImageSharp.Formats this.huffmanTrees[i].Dispose(); } + this.ycbcrImage?.Dispose(); this.bytes.Dispose(); this.grayImage.ReturnPooled(); this.blackImage.ReturnPooled(); @@ -2059,8 +2060,8 @@ namespace ImageSharp.Formats if (this.componentCount == 1) { - JpegPixelArea gray = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy); - this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight); + this.grayImage = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy); + //this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight); } else { @@ -2092,9 +2093,8 @@ namespace ImageSharp.Formats break; } - this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio); - //this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight); - + this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio, 0, 0); + if (this.componentCount == 4) { int h3 = this.componentArray[3].HorizontalFactor; diff --git a/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs b/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs similarity index 88% rename from src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs rename to src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs index cb3cd0fe5..a181eeee0 100644 --- a/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs +++ b/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs @@ -2,7 +2,7 @@ { using System.Buffers; - internal class ArrayPoolManager + internal class CleanPooler { private static readonly ArrayPool Pool = ArrayPool.Create(); From 6b4f69c1fbb7c1378a0dbe6b80d8dc8efa05eddb Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Mon, 26 Dec 2016 19:09:53 +0100 Subject: [PATCH 05/24] moved Huffman table related code to HuffmanTree --- .../Jpg/Components/Decoder/HuffmanTree.cs | 154 ++++++++++++++++-- .../Jpg/Components/Decoder/JpegPixelArea.cs | 4 +- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 150 ++--------------- 3 files changed, 156 insertions(+), 152 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs index a148cc558..2c08284a3 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs @@ -12,6 +12,33 @@ namespace ImageSharp.Formats.Jpg /// internal struct HuffmanTree : IDisposable { + public static HuffmanTree[] CreateHuffmanTrees() + { + HuffmanTree[] result = new HuffmanTree[(MaxTc + 1) * (MaxTh + 1)]; + for (int i = 0; i < MaxTc + 1; i++) + { + for (int j = 0; j < MaxTh + 1; j++) + { + result[(i * ThRowSize) + j].Init(); + } + } + return result; + } + /// + /// The maximum (inclusive) number of codes in a Huffman tree. + /// + internal const int MaxNCodes = 256; + + /// + /// The maximum (inclusive) number of bits in a Huffman code. + /// + internal const int MaxCodeLength = 16; + + /// + /// The log-2 size of the Huffman decoder's look-up table. + /// + internal const int LutSize = 8; + /// /// Gets or sets the number of codes in the tree. /// @@ -47,25 +74,22 @@ namespace ImageSharp.Formats.Jpg /// public int[] Indices; - private static readonly ArrayPool UshortBuffer = ArrayPool.Create(1 << JpegDecoderCore.LutSize, 50); + private static readonly ArrayPool UshortBuffer = ArrayPool.Create(1 << LutSize, 50); - private static readonly ArrayPool ByteBuffer = ArrayPool.Create(JpegDecoderCore.MaxNCodes, 50); + private static readonly ArrayPool ByteBuffer = ArrayPool.Create(MaxNCodes, 50); - private static readonly ArrayPool IntBuffer = ArrayPool.Create(JpegDecoderCore.MaxCodeLength, 50); + private static readonly ArrayPool IntBuffer = ArrayPool.Create(MaxCodeLength, 50); /// /// Initializes the Huffman tree /// - /// Lut size - /// Max N codes - /// Max code length - public void Init(int lutSize, int maxNCodes, int maxCodeLength) + public void Init() { - this.Lut = UshortBuffer.Rent(1 << lutSize); - this.Values = ByteBuffer.Rent(maxNCodes); - this.MinCodes = IntBuffer.Rent(maxCodeLength); - this.MaxCodes = IntBuffer.Rent(maxCodeLength); - this.Indices = IntBuffer.Rent(maxCodeLength); + this.Lut = UshortBuffer.Rent(1 << LutSize); + this.Values = ByteBuffer.Rent(MaxNCodes); + this.MinCodes = IntBuffer.Rent(MaxCodeLength); + this.MaxCodes = IntBuffer.Rent(MaxCodeLength); + this.Indices = IntBuffer.Rent(MaxCodeLength); } /// @@ -79,5 +103,111 @@ namespace ImageSharp.Formats.Jpg IntBuffer.Return(this.MaxCodes, true); IntBuffer.Return(this.Indices, true); } + + /// + /// Internal part of the DHT processor, whatever does it mean + /// + /// The decoder instance + /// The temporal buffer that holds the data that has been read from stream + /// Remaining bits + internal void ProcessDefineHuffmanTablesMarkerLoop(JpegDecoderCore decoder, byte[] defineHuffmanTablesData, 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. + this.Length = 0; + + int[] ncodes = new int[MaxCodeLength]; + for (int i = 0; i < ncodes.Length; i++) + { + ncodes[i] = defineHuffmanTablesData[i + 1]; + this.Length += ncodes[i]; + } + + if (this.Length == 0) + { + throw new ImageFormatException("Huffman table has zero length"); + } + + if (this.Length > MaxNCodes) + { + throw new ImageFormatException("Huffman table has excessive length"); + } + + remaining -= this.Length + 17; + if (remaining < 0) + { + throw new ImageFormatException("DHT has wrong length"); + } + + decoder.ReadFull(this.Values, 0, this.Length); + + // Derive the look-up table. + for (int i = 0; i < this.Lut.Length; i++) + { + this.Lut[i] = 0; + } + + uint x = 0, code = 0; + + for (int i = 0; i < LutSize; i++) + { + code <<= 1; + + for (int j = 0; j < ncodes[i]; j++) + { + // The codeLength is 1+i, so shift code by 8-(1+i) to + // calculate the high bits for every 8-bit sequence + // whose codeLength's high bits matches code. + // 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)((this.Values[x] << 8) | (2 + i)); + + for (int k = 0; k < 1 << (7 - i); k++) + { + this.Lut[base2 | k] = lutValue; + } + + code++; + x++; + } + } + + // Derive minCodes, maxCodes, and indices. + int c = 0, index = 0; + for (int i = 0; i < ncodes.Length; i++) + { + int nc = ncodes[i]; + if (nc == 0) + { + this.MinCodes[i] = -1; + this.MaxCodes[i] = -1; + this.Indices[i] = -1; + } + else + { + this.MinCodes[i] = c; + this.MaxCodes[i] = c + nc - 1; + this.Indices[i] = index; + c += nc; + index += nc; + } + + c <<= 1; + } + } + + /// + /// The maximum number of Huffman table classes + /// + internal const int MaxTc = 1; + + /// + /// The maximum number of Huffman table identifiers + /// + internal const int MaxTh = 3; + + internal const int ThRowSize = MaxTh + 1; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs index 0161542d8..92d0f3429 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs @@ -23,8 +23,6 @@ namespace ImageSharp.Formats.Jpg public static JpegPixelArea CreatePooled(int width, int height) { int size = width * height; - //var pixels = ArrayPool.Shared.Rent(size); - //Array.Clear(pixels, 0, size); var pixels = CleanPooler.RentCleanArray(size); return new JpegPixelArea(pixels, width, 0); } @@ -61,7 +59,7 @@ namespace ImageSharp.Formats.Jpg public int Offset { get; private set; } /// - /// Get the subarea that belongs to the given block indices + /// Get the subarea that belongs to the Block8x8 defined by block indices /// /// The block X index /// The block Y index diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 08fcb1018..9e5990d2d 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -17,38 +17,11 @@ namespace ImageSharp.Formats /// internal unsafe class JpegDecoderCore : IDisposable { - /// - /// The maximum (inclusive) number of bits in a Huffman code. - /// - internal const int MaxCodeLength = 16; - - /// - /// The maximum (inclusive) number of codes in a Huffman tree. - /// - internal const int MaxNCodes = 256; - - /// - /// The log-2 size of the Huffman decoder's look-up table. - /// - internal const int LutSize = 8; - /// /// The maximum number of color components /// private const int MaxComponents = 4; - /// - /// The maximum number of Huffman table classes - /// - private const int MaxTc = 1; - - /// - /// The maximum number of Huffman table identifiers - /// - private const int MaxTh = 3; - - private const int ThRowSize = MaxTh + 1; - /// /// The maximum number of quantization tables /// @@ -87,7 +60,7 @@ namespace ImageSharp.Formats /// /// A temporary buffer for holding pixels /// - private readonly byte[] temp; + private readonly byte[] temp; // TODO: the usage of this buffer is unclean + need to move it to the stack for performance /// /// The byte buffer. @@ -179,24 +152,13 @@ namespace ImageSharp.Formats /// public JpegDecoderCore() { - // this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1]; - this.huffmanTrees = new HuffmanTree[(MaxTc + 1) * (MaxTh + 1)]; - + this.huffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.quantizationTables = new Block8x8F[MaxTq + 1]; this.temp = new byte[2 * Block8x8F.ScalarCount]; this.componentArray = new Component[MaxComponents]; this.progCoeffs = new Block8x8F[MaxComponents][]; this.bits = default(Bits); this.bytes = Bytes.Create(); - - // TODO: This looks like it could be static. - for (int i = 0; i < MaxTc + 1; i++) - { - for (int j = 0; j < MaxTh + 1; j++) - { - this.huffmanTrees[(i * ThRowSize) + j].Init(LutSize, MaxNCodes, MaxCodeLength); - } - } } /// @@ -528,108 +490,22 @@ namespace ImageSharp.Formats this.ReadFull(this.temp, 0, 17); int tc = this.temp[0] >> 4; - if (tc > MaxTc) + if (tc > HuffmanTree.MaxTc) { throw new ImageFormatException("Bad Tc value"); } int th = this.temp[0] & 0x0f; - if (th > MaxTh || (!this.isProgressive && (th > 1))) + if (th > HuffmanTree.MaxTh || (!this.isProgressive && (th > 1))) { throw new ImageFormatException("Bad Th value"); } - this.ProcessDefineHuffmanTablesMarkerLoop(ref this.huffmanTrees[(tc * ThRowSize) + th], ref remaining); + int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th; + this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.temp, ref 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. - huffmanTree.Length = 0; - - int[] ncodes = new int[MaxCodeLength]; - for (int i = 0; i < ncodes.Length; i++) - { - ncodes[i] = this.temp[i + 1]; - huffmanTree.Length += ncodes[i]; - } - - if (huffmanTree.Length == 0) - { - throw new ImageFormatException("Huffman table has zero length"); - } - - if (huffmanTree.Length > MaxNCodes) - { - throw new ImageFormatException("Huffman table has excessive length"); - } - - remaining -= huffmanTree.Length + 17; - if (remaining < 0) - { - throw new ImageFormatException("DHT has wrong length"); - } - - this.ReadFull(huffmanTree.Values, 0, huffmanTree.Length); - - // Derive the look-up table. - for (int i = 0; i < huffmanTree.Lut.Length; i++) - { - huffmanTree.Lut[i] = 0; - } - - uint x = 0, code = 0; - - for (int i = 0; i < LutSize; i++) - { - code <<= 1; - - for (int j = 0; j < ncodes[i]; j++) - { - // The codeLength is 1+i, so shift code by 8-(1+i) to - // calculate the high bits for every 8-bit sequence - // whose codeLength's high bits matches code. - // 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)((huffmanTree.Values[x] << 8) | (2 + i)); - - for (int k = 0; k < 1 << (7 - i); k++) - { - huffmanTree.Lut[base2 | k] = lutValue; - } - - code++; - x++; - } - } - - // Derive minCodes, maxCodes, and indices. - int c = 0, index = 0; - for (int i = 0; i < ncodes.Length; i++) - { - int nc = ncodes[i]; - if (nc == 0) - { - huffmanTree.MinCodes[i] = -1; - huffmanTree.MaxCodes[i] = -1; - huffmanTree.Indices[i] = -1; - } - else - { - huffmanTree.MinCodes[i] = c; - huffmanTree.MaxCodes[i] = c + nc - 1; - huffmanTree.Indices[i] = index; - c += nc; - index += nc; - } - - c <<= 1; - } - } /// /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. @@ -650,7 +526,7 @@ namespace ImageSharp.Formats if (errorCode == ErrorCodes.NoError) { - ushort v = huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff]; + ushort v = huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - HuffmanTree.LutSize)) & 0xff]; if (v != 0) { @@ -667,7 +543,7 @@ namespace ImageSharp.Formats } int code = 0; - for (int i = 0; i < MaxCodeLength; i++) + for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) { if (this.bits.UnreadBits == 0) { @@ -766,7 +642,7 @@ namespace ImageSharp.Formats /// The data to write to. /// The offset in the source buffer /// The number of bytes to read - private void ReadFull(byte[] data, int offset, int length) + internal void ReadFull(byte[] data, int offset, int length) { // Unread the overshot bytes, if any. if (this.bytes.UnreadableBytes != 0) @@ -1710,7 +1586,7 @@ namespace ImageSharp.Formats int bx, Block8x8F* qt) { - int huffmannIdx = (AcTable * ThRowSize) + scan[i].AcTableSelector; + int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector; if (ah != 0) { this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al); @@ -1724,7 +1600,7 @@ namespace ImageSharp.Formats // Decode the DC coefficient, as specified in section F.2.2.1. byte value = this.DecodeHuffman( - ref this.huffmanTrees[(DcTable * ThRowSize) + scan[i].DcTableSelector]); + ref this.huffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); if (value > 16) { throw new ImageFormatException("Excessive DC component"); @@ -1884,13 +1760,13 @@ namespace ImageSharp.Formats totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor; currentScan.DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); - if (currentScan.DcTableSelector > MaxTh) + if (currentScan.DcTableSelector > HuffmanTree.MaxTh) { throw new ImageFormatException("Bad DC table selector value"); } currentScan.AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); - if (currentScan.AcTableSelector > MaxTh) + if (currentScan.AcTableSelector > HuffmanTree.MaxTh) { throw new ImageFormatException("Bad AC table selector value"); } From 83b4e56810e7810cfcca0ca280d9da39177a40a7 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Mon, 26 Dec 2016 19:56:11 +0100 Subject: [PATCH 06/24] cleanup & docs --- .../Jpg/Components/Decoder/HuffmanTree.cs | 109 +- .../Jpg/Components/Decoder/JpegPixelArea.cs | 124 +- .../Jpg/Components/Decoder/YCbCrImage.cs | 134 +- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 2093 +++++++++-------- .../Formats/Jpg/Utils/CleanPooler.cs | 19 +- 5 files changed, 1274 insertions(+), 1205 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs index 2c08284a3..12acdfc6c 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs @@ -8,69 +8,77 @@ namespace ImageSharp.Formats.Jpg using System.Buffers; /// - /// Represents a Huffman tree + /// Represents a Huffman tree /// internal struct HuffmanTree : IDisposable { - public static HuffmanTree[] CreateHuffmanTrees() - { - HuffmanTree[] result = new HuffmanTree[(MaxTc + 1) * (MaxTh + 1)]; - for (int i = 0; i < MaxTc + 1; i++) - { - for (int j = 0; j < MaxTh + 1; j++) - { - result[(i * ThRowSize) + j].Init(); - } - } - return result; - } /// - /// The maximum (inclusive) number of codes in a Huffman tree. + /// The maximum (inclusive) number of codes in a Huffman tree. + /// + public const int MaxNCodes = 256; + + /// + /// The maximum (inclusive) number of bits in a Huffman code. + /// + public const int MaxCodeLength = 16; + + /// + /// The maximum number of Huffman table classes + /// + public const int MaxTc = 1; + + /// + /// The maximum number of Huffman table identifiers + /// + public const int MaxTh = 3; + + /// + /// Row size of the Huffman table /// - internal const int MaxNCodes = 256; + public const int ThRowSize = MaxTh + 1; /// - /// The maximum (inclusive) number of bits in a Huffman code. + /// Number of Hufman Trees in the Huffman table /// - internal const int MaxCodeLength = 16; + public const int NumberOfTrees = (MaxTc + 1) * (MaxTh + 1); /// - /// The log-2 size of the Huffman decoder's look-up table. + /// The log-2 size of the Huffman decoder's look-up table. /// - internal const int LutSize = 8; + public const int LutSize = 8; /// - /// Gets or sets the number of codes in the tree. + /// Gets or sets the number of codes in the tree. /// public int Length; /// - /// Gets the look-up table for the next LutSize bits in the bit-stream. - /// The high 8 bits of the uint16 are the encoded value. The low 8 bits - /// are 1 plus the code length, or 0 if the value is too large to fit in - /// lutSize bits. + /// Gets the look-up table for the next LutSize bits in the bit-stream. + /// The high 8 bits of the uint16 are the encoded value. The low 8 bits + /// are 1 plus the code length, or 0 if the value is too large to fit in + /// lutSize bits. /// public ushort[] Lut; /// - /// Gets the the decoded values, sorted by their encoding. + /// Gets the the decoded values, sorted by their encoding. /// public byte[] Values; /// - /// Gets the array of minimum codes. - /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. + /// Gets the array of minimum codes. + /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. /// public int[] MinCodes; /// - /// Gets the array of maximum codes. - /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. + /// Gets the array of maximum codes. + /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. /// public int[] MaxCodes; /// - /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. + /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// public int[] Indices; @@ -81,7 +89,25 @@ namespace ImageSharp.Formats.Jpg private static readonly ArrayPool IntBuffer = ArrayPool.Create(MaxCodeLength, 50); /// - /// Initializes the Huffman tree + /// Creates and initializes an array of instances of size + /// + /// An array of instances representing the Huffman table + public static HuffmanTree[] CreateHuffmanTrees() + { + HuffmanTree[] result = new HuffmanTree[NumberOfTrees]; + for (int i = 0; i < MaxTc + 1; i++) + { + for (int j = 0; j < MaxTh + 1; j++) + { + result[(i * ThRowSize) + j].Init(); + } + } + + return result; + } + + /// + /// Initializes the Huffman tree /// public void Init() { @@ -93,7 +119,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Disposes the underlying buffers + /// Disposes the underlying buffers /// public void Dispose() { @@ -105,12 +131,15 @@ namespace ImageSharp.Formats.Jpg } /// - /// Internal part of the DHT processor, whatever does it mean + /// Internal part of the DHT processor, whatever does it mean /// /// The decoder instance /// The temporal buffer that holds the data that has been read from stream /// Remaining bits - internal void ProcessDefineHuffmanTablesMarkerLoop(JpegDecoderCore decoder, byte[] defineHuffmanTablesData, ref int remaining) + internal void ProcessDefineHuffmanTablesMarkerLoop( + JpegDecoderCore decoder, + byte[] defineHuffmanTablesData, + ref int remaining) { // Read nCodes and huffman.Valuess (and derive h.Length). // nCodes[i] is the number of codes with code length i. @@ -197,17 +226,5 @@ namespace ImageSharp.Formats.Jpg c <<= 1; } } - - /// - /// The maximum number of Huffman table classes - /// - internal const int MaxTc = 1; - - /// - /// The maximum number of Huffman table identifiers - /// - internal const int MaxTh = 3; - - internal const int ThRowSize = MaxTh + 1; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs index 92d0f3429..00e2b0e13 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs @@ -1,89 +1,127 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp.Formats.Jpg { - - using System; - using System.Buffers; using System.Runtime.CompilerServices; /// - /// Represents a grayscale image + /// Represents an area of a Jpeg subimage (channel) /// internal struct JpegPixelArea { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct from existing data. /// - /// The width. - /// The height. - public static JpegPixelArea CreatePooled(int width, int height) - { - int size = width * height; - var pixels = CleanPooler.RentCleanArray(size); - return new JpegPixelArea(pixels, width, 0); - } - - public JpegPixelArea(byte[] pixels, int widthOrStride, int offset) + /// The pixel array + /// The stride + /// The offset + public JpegPixelArea(byte[] pixels, int striede, int offset) { - this.Stride = widthOrStride; + this.Stride = striede; this.Pixels = pixels; this.Offset = offset; } - public void ReturnPooled() - { - if (this.Pixels == null) return; - CleanPooler.ReturnArray(this.Pixels); - this.Pixels = null; - } - /// - /// Gets or sets the pixels. + /// Gets the pixels. /// public byte[] Pixels { get; private set; } - public bool Created => this.Pixels != null; + /// + /// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea)) + /// + public bool IsInitialized => this.Pixels != null; /// - /// Gets or sets the width. + /// Gets or the stride. /// - public int Stride { get; private set; } + public int Stride { get; } /// - /// Gets or sets the offset + /// Gets or the offset. /// - public int Offset { get; private set; } - + public int Offset { get; } + /// - /// Get the subarea that belongs to the Block8x8 defined by block indices + /// Gets a of bytes to the pixel area + /// + public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset); + + /// + /// Returns the pixel at (x, y) + /// + /// The x index + /// The y index + /// The pixel value + public byte this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return this.Pixels[(y * this.Stride) + x]; + } + } + + /// + /// Creates a new instance of the struct. + /// Pixel array will be taken from a pool, this instance will be the owner of it's pixel data, therefore + /// should be called when the instance is no longer needed. + /// + /// The width. + /// The height. + /// A with pooled data + public static JpegPixelArea CreatePooled(int width, int height) + { + int size = width * height; + var pixels = CleanPooler.RentCleanArray(size); + return new JpegPixelArea(pixels, width, 0); + } + + /// + /// Returns to the pool + /// + public void ReturnPooled() + { + if (this.Pixels == null) + { + return; + } + + CleanPooler.ReturnArray(this.Pixels); + this.Pixels = null; + } + + /// + /// Gets the subarea that belongs to the Block8x8 defined by block indices /// /// The block X index /// The block Y index - /// - public JpegPixelArea GetOffsetedAreaForBlock(int bx, int by) + /// The subarea offseted by block indices + public JpegPixelArea GetOffsetedSubAreaForBlock(int bx, int by) { - int offset = this.Offset + 8 * (by * this.Stride + bx); + int offset = this.Offset + (8 * ((by * this.Stride) + bx)); return new JpegPixelArea(this.Pixels, this.Stride, offset); } - public byte this[int x, int y] => this.Pixels[y * this.Stride + x]; - /// - /// Gets the row offset at the given position + /// Gets the row offset at the given position /// /// The y-coordinate of the image. - /// The + /// The public int GetRowOffset(int y) { return this.Offset + (y * this.Stride); } - public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset); - + /// + /// Load values to the pixel area from the given . + /// Level shift [-128.0, 128.0] floating point color values by +128, clip them to [0, 255], and convert them to + /// values + /// + /// The block holding the color values + /// Temporal block provided by the caller [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp) { @@ -91,4 +129,4 @@ namespace ImageSharp.Formats.Jpg block->CopyColorsTo(new MutableSpan(this.Pixels, this.Offset), this.Stride, temp); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs index bd0d58dd2..f842a295c 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs @@ -2,24 +2,22 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp.Formats.Jpg { using System; - using System.Buffers; /// - /// Represents an image made up of three color components (luminance, blue chroma, red chroma) + /// Represents an image made up of three color components (luminance, blue chroma, red chroma) /// internal class YCbCrImage : IDisposable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The width. /// The height. /// The ratio. - public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio, int yOffset, int cOffset) + public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio) { int cw, ch; YCbCrSize(width, height, ratio, out cw, out ch); @@ -27,134 +25,120 @@ namespace ImageSharp.Formats.Jpg this.CbPixels = CleanPooler.RentCleanArray(cw * ch); this.CrPixels = CleanPooler.RentCleanArray(cw * ch); this.Ratio = ratio; - this.YOffset = yOffset; - this.COffset = cOffset; + this.YOffset = 0; + this.COffset = 0; this.YStride = width; this.CStride = cw; - this.X = 0; - this.Y = 0; } - public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset); - - public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset); - - public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset); - - /// - /// Prevents a default instance of the class from being created. - /// - private YCbCrImage(int yOffset, int cOffset) - { - this.YOffset = yOffset; - this.COffset = cOffset; - } - /// - /// Provides enumeration of the various available subsample ratios. + /// Provides enumeration of the various available subsample ratios. /// public enum YCbCrSubsampleRatio { /// - /// YCbCrSubsampleRatio444 + /// YCbCrSubsampleRatio444 /// YCbCrSubsampleRatio444, /// - /// YCbCrSubsampleRatio422 + /// YCbCrSubsampleRatio422 /// YCbCrSubsampleRatio422, /// - /// YCbCrSubsampleRatio420 + /// YCbCrSubsampleRatio420 /// YCbCrSubsampleRatio420, /// - /// YCbCrSubsampleRatio440 + /// YCbCrSubsampleRatio440 /// YCbCrSubsampleRatio440, /// - /// YCbCrSubsampleRatio411 + /// YCbCrSubsampleRatio411 /// YCbCrSubsampleRatio411, /// - /// YCbCrSubsampleRatio410 + /// YCbCrSubsampleRatio410 /// YCbCrSubsampleRatio410, } /// - /// Gets or sets the luminance components channel. + /// Gets an offseted to the Cb channel /// - public byte[] YPixels { get; } + public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset); /// - /// Gets or sets the blue chroma components channel. + /// Gets the blue chroma components channel. /// public byte[] CbPixels { get; } /// - /// Gets or sets the red chroma components channel. + /// Gets the index of the first element of red or blue chroma. /// - public byte[] CrPixels { get; } + public int COffset { get; } /// - /// Gets or sets the Y slice index delta between vertically adjacent pixels. + /// Gets an offseted to the Cr channel /// - public int YStride { get; } + public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset); /// - /// Gets or sets the red and blue chroma slice index delta between vertically adjacent pixels - /// that map to separate chroma samples. + /// Gets the red chroma components channel. + /// + public byte[] CrPixels { get; } + + /// + /// Gets the red and blue chroma slice index delta between vertically adjacent pixels + /// that map to separate chroma samples. /// public int CStride { get; } /// - /// Gets or sets the index of the first luminance element. + /// Gets or sets the subsampling ratio. /// - public int YOffset { get; } + public YCbCrSubsampleRatio Ratio { get; set; } /// - /// Gets or sets the index of the first element of red or blue chroma. + /// Gets an offseted to the Y channel /// - public int COffset { get; } + public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset); /// - /// Gets or sets the horizontal position. + /// Gets the index of the first luminance element. /// - public int X { get; set; } + public int YOffset { get; } /// - /// Gets or sets the vertical position. + /// Gets the luminance components channel. /// - public int Y { get; set; } - + public byte[] YPixels { get; } + /// - /// Gets or sets the subsampling ratio. + /// Gets the Y slice index delta between vertically adjacent pixels. /// - public YCbCrSubsampleRatio Ratio { get; set; } + public int YStride { get; } /// - /// Returns the offset of the first luminance component at the given row + /// Disposes the returning rented arrays to the pools. /// - /// The row number. - /// - /// The . - /// - public int GetRowYOffset(int y) + public void Dispose() { - return y * this.YStride; + CleanPooler.ReturnArray(this.YPixels); + CleanPooler.ReturnArray(this.CrPixels); + CleanPooler.ReturnArray(this.CbPixels); } /// - /// Returns the offset of the first chroma component at the given row + /// Returns the offset of the first chroma component at the given row /// /// The row number. /// - /// The . + /// The . /// public int GetRowCOffset(int y) { @@ -176,14 +160,31 @@ namespace ImageSharp.Formats.Jpg } /// - /// Returns the height and width of the chroma components + /// Returns the offset of the first luminance component at the given row + /// + /// The row number. + /// + /// The . + /// + public int GetRowYOffset(int y) + { + return y * this.YStride; + } + + /// + /// Returns the height and width of the chroma components /// /// The width. /// The height. /// The subsampling ratio. /// The chroma width. /// The chroma height. - private static void YCbCrSize(int width, int height, YCbCrSubsampleRatio ratio, out int chromaWidth, out int chromaHeight) + private static void YCbCrSize( + int width, + int height, + YCbCrSubsampleRatio ratio, + out int chromaWidth, + out int chromaHeight) { switch (ratio) { @@ -215,12 +216,5 @@ namespace ImageSharp.Formats.Jpg break; } } - - public void Dispose() - { - CleanPooler.ReturnArray(this.YPixels); - CleanPooler.ReturnArray(this.CrPixels); - CleanPooler.ReturnArray(this.CbPixels); - } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 9e5990d2d..1696d0811 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -2,153 +2,153 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp.Formats { using System; using System.IO; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using System.Threading.Tasks; + using ImageSharp.Formats.Jpg; /// - /// Performs the jpeg decoding operation. + /// Performs the jpeg decoding operation. /// internal unsafe class JpegDecoderCore : IDisposable { /// - /// The maximum number of color components + /// The AC table index /// - private const int MaxComponents = 4; + private const int AcTable = 1; /// - /// The maximum number of quantization tables + /// The DC table index /// - private const int MaxTq = 3; + private const int DcTable = 0; /// - /// The DC table index + /// The maximum number of color components /// - private const int DcTable = 0; + private const int MaxComponents = 4; /// - /// The AC table index + /// The maximum number of quantization tables /// - private const int AcTable = 1; + private const int MaxTq = 3; /// - /// The component array + /// The component array /// private readonly Component[] componentArray; /// - /// Saved state between progressive-mode scans. + /// The huffman trees /// - private readonly Block8x8F[][] progCoeffs; + private readonly HuffmanTree[] huffmanTrees; /// - /// The huffman trees + /// Saved state between progressive-mode scans. /// - private readonly HuffmanTree[] huffmanTrees; + private readonly Block8x8F[][] progCoeffs; /// - /// Quantization tables, in zigzag order. + /// Quantization tables, in zigzag order. /// private readonly Block8x8F[] quantizationTables; /// - /// A temporary buffer for holding pixels + /// A temporary buffer for holding pixels /// - private readonly byte[] temp; // TODO: the usage of this buffer is unclean + need to move it to the stack for performance + private readonly byte[] temp; + + // TODO: the usage of this buffer is unclean + need to move it to the stack for performance /// - /// The byte buffer. + /// The App14 marker color-space /// - private Bytes bytes; + private byte adobeTransform; /// - /// The byte buffer. + /// Whether the image is in CMYK format with an App14 marker /// - private Stream inputStream; + private bool adobeTransformValid; /// - /// Holds the unprocessed bits that have been taken from the byte-stream. + /// Holds the unprocessed bits that have been taken from the byte-stream. /// private Bits bits; - /// - /// The image width - /// - private int imageWidth; + private JpegPixelArea blackImage; + + private int blockIndex; /// - /// The image height + /// The byte buffer. /// - private int imageHeight; + private Bytes bytes; /// - /// The number of color components within the image. + /// The number of color components within the image. /// private int componentCount; /// - /// A grayscale image to decode to. + /// End-of-Band run, specified in section G.1.2.2. /// - private JpegPixelArea grayImage; + private ushort eobRun; /// - /// The full color image to decode to. + /// A grayscale image to decode to. /// - private YCbCrImage ycbcrImage; - - - private JpegPixelArea blackImage; + private JpegPixelArea grayImage; /// - /// The restart interval + /// The horizontal resolution. Calculated if the image has a JFIF header. /// - private int restartInterval; + private short horizontalResolution; /// - /// Whether the image is interlaced (progressive) + /// The image height /// - private bool isProgressive; + private int imageHeight; /// - /// Whether the image has a JFIF header + /// The image width /// - private bool isJfif; + private int imageWidth; /// - /// Whether the image is in CMYK format with an App14 marker + /// The byte buffer. /// - private bool adobeTransformValid; + private Stream inputStream; /// - /// The App14 marker color-space + /// Whether the image has a JFIF header /// - private byte adobeTransform; + private bool isJfif; /// - /// End-of-Band run, specified in section G.1.2.2. + /// Whether the image is interlaced (progressive) /// - private ushort eobRun; + private bool isProgressive; /// - /// The horizontal resolution. Calculated if the image has a JFIF header. + /// The restart interval /// - private short horizontalResolution; + private int restartInterval; /// - /// The vertical resolution. Calculated if the image has a JFIF header. + /// The vertical resolution. Calculated if the image has a JFIF header. /// private short verticalResolution; - private int blockIndex; + /// + /// The full color image to decode to. + /// + private YCbCrImage ycbcrImage; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public JpegDecoderCore() { @@ -162,24 +162,24 @@ namespace ImageSharp.Formats } /// - /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent) - /// It's better tho have an error code for this! + /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent) + /// It's better tho have an error code for this! /// internal enum ErrorCodes { /// - /// NoError + /// NoError /// NoError, /// - /// MissingFF00 + /// MissingFF00 /// MissingFF00 } /// - /// Gets or sets the byte buffer. + /// Gets or sets the byte buffer. /// public Bytes Bytes { @@ -195,7 +195,7 @@ namespace ImageSharp.Formats } /// - /// Gets the input stream. + /// Gets the input stream. /// public Stream InputStream { @@ -206,8 +206,8 @@ namespace ImageSharp.Formats } /// - /// Decodes the image from the specified this._stream and sets - /// the data to image. + /// Decodes the image from the specified this._stream and sets + /// the data to image. /// /// The pixel format. /// The image, where the data should be set to. @@ -375,7 +375,7 @@ namespace ImageSharp.Formats } } - if (this.grayImage.Created) + if (this.grayImage.IsInitialized) { this.ConvertFromGrayScale(this.imageWidth, this.imageHeight, image); } @@ -385,7 +385,8 @@ namespace ImageSharp.Formats { if (!this.adobeTransformValid) { - throw new ImageFormatException("Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); + throw new ImageFormatException( + "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); } // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe @@ -425,7 +426,7 @@ namespace ImageSharp.Formats } /// - /// Dispose + /// Dispose /// public void Dispose() { @@ -441,9 +442,9 @@ namespace ImageSharp.Formats } /// - /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. + /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. /// - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] internal byte ReadByte() { @@ -451,8 +452,47 @@ namespace ImageSharp.Formats } /// - /// Optimized method to pack bytes to the image from the YCbCr color space. - /// This is faster than implicit casting as it avoids double packing. + /// Reads exactly length bytes into data. It does not care about byte stuffing. + /// + /// The data to write to. + /// The offset in the source buffer + /// The number of bytes to read + internal void ReadFull(byte[] data, int offset, int length) + { + // Unread the overshot bytes, if any. + if (this.bytes.UnreadableBytes != 0) + { + if (this.bits.UnreadBits >= 8) + { + this.UnreadByteStuffedByte(); + } + + this.bytes.UnreadableBytes = 0; + } + + while (length > 0) + { + if (this.bytes.J - this.bytes.I >= length) + { + Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, length); + this.bytes.I += length; + length -= length; + } + else + { + Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, this.bytes.J - this.bytes.I); + offset += this.bytes.J - this.bytes.I; + length -= this.bytes.J - this.bytes.I; + this.bytes.I += this.bytes.J - this.bytes.I; + + this.bytes.Fill(this.inputStream); + } + } + } + + /// + /// Optimized method to pack bytes to the image from the YCbCr color space. + /// This is faster than implicit casting as it avoids double packing. /// /// The pixel format. /// The packed pixel. @@ -474,440 +514,794 @@ namespace ImageSharp.Formats } /// - /// Processes a Define Huffman Table marker, and initializes a huffman - /// struct from its contents. Specified in section B.2.4.2. + /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. /// - /// The remaining bytes in the segment block. - private void ProcessDefineHuffmanTablesMarker(int remaining) + /// The pixel format. + /// The image to assign the resolution to. + private void AssignResolution(Image image) + where TColor : struct, IPackedPixel, IEquatable { - while (remaining > 0) + if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) { - if (remaining < 17) - { - throw new ImageFormatException("DHT has wrong length"); - } - - this.ReadFull(this.temp, 0, 17); - - int tc = this.temp[0] >> 4; - if (tc > HuffmanTree.MaxTc) - { - throw new ImageFormatException("Bad Tc value"); - } - - int th = this.temp[0] & 0x0f; - if (th > HuffmanTree.MaxTh || (!this.isProgressive && (th > 1))) - { - throw new ImageFormatException("Bad Th value"); - } - - int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th; - this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.temp, ref remaining); + image.HorizontalResolution = this.horizontalResolution; + image.VerticalResolution = this.verticalResolution; } } - /// - /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. + /// Converts the image from the original CMYK image pixels. /// - /// The huffman value - /// The - private byte DecodeHuffman(ref HuffmanTree huffmanTree) + /// The pixel format. + /// The image width. + /// The image height. + /// The image. + private void ConvertFromCmyk(int width, int height, Image image) + where TColor : struct, IPackedPixel, IEquatable { - // Copy stuff to the stack: - if (huffmanTree.Length == 0) - { - throw new ImageFormatException("Uninitialized Huffman table"); - } - - if (this.bits.UnreadBits < 8) - { - ErrorCodes errorCode = this.bits.EnsureNBits(8, this); - - if (errorCode == ErrorCodes.NoError) - { - ushort v = huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - HuffmanTree.LutSize)) & 0xff]; + int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; - if (v != 0) - { - byte n = (byte)((v & 0xff) - 1); - this.bits.UnreadBits -= n; - this.bits.Mask >>= n; - return (byte)(v >> 8); - } - } - else - { - this.UnreadByteStuffedByte(); - } - } + image.InitPixels(width, height); - int code = 0; - for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) + using (PixelAccessor pixels = image.Lock()) { - if (this.bits.UnreadBits == 0) - { - ErrorCodes errorCode = this.bits.EnsureNBits(1, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } - } - - if ((this.bits.Accumulator & this.bits.Mask) != 0) - { - code |= 1; - } - - this.bits.UnreadBits--; - this.bits.Mask >>= 1; + Parallel.For( + 0, + height, + y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); - if (code <= huffmanTree.MaxCodes[i]) - { - return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]]; - } + for (int x = 0; x < width; x++) + { + byte cyan = this.ycbcrImage.YPixels[yo + x]; + byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)]; - code <<= 1; + TColor packed = default(TColor); + this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); + pixels[x, y] = packed; + } + }); } - throw new ImageFormatException("Bad Huffman code"); + this.AssignResolution(image); } /// - /// Decodes a single bit + /// Converts the image from the original grayscale image pixels. /// - /// The - private bool DecodeBit() + /// The pixel format. + /// The image width. + /// The image height. + /// The image. + private void ConvertFromGrayScale(int width, int height, Image image) + where TColor : struct, IPackedPixel, IEquatable { - if (this.bits.UnreadBits == 0) + image.InitPixels(width, height); + + using (PixelAccessor pixels = image.Lock()) { - ErrorCodes errorCode = this.bits.EnsureNBits(1, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } + Parallel.For( + 0, + height, + Bootstrapper.ParallelOptions, + y => + { + int yoff = this.grayImage.GetRowOffset(y); + for (int x = 0; x < width; x++) + { + byte rgb = this.grayImage.Pixels[yoff + x]; + + TColor packed = default(TColor); + packed.PackFromBytes(rgb, rgb, rgb, 255); + pixels[x, y] = packed; + } + }); } - bool ret = (this.bits.Accumulator & this.bits.Mask) != 0; - this.bits.UnreadBits--; - this.bits.Mask >>= 1; - return ret; + this.AssignResolution(image); } /// - /// Decodes the given number of bits + /// Converts the image from the original RBG image pixels. /// - /// The number of bits to decode. - /// The - private uint DecodeBits(int count) + /// The pixel format. + /// The image width. + /// The height. + /// The image. + private void ConvertFromRGB(int width, int height, Image image) + where TColor : struct, IPackedPixel, IEquatable { - if (this.bits.UnreadBits < count) - { - ErrorCodes errorCode = this.bits.EnsureNBits(count, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } - } + int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + image.InitPixels(width, height); - uint ret = this.bits.Accumulator >> (this.bits.UnreadBits - count); - ret = (uint)(ret & ((1 << count) - 1)); - this.bits.UnreadBits -= count; - this.bits.Mask >>= count; - return ret; - } + using (PixelAccessor pixels = image.Lock()) + { + Parallel.For( + 0, + height, + Bootstrapper.ParallelOptions, + y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) + { + byte red = this.ycbcrImage.YPixels[yo + x]; + byte green = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte blue = this.ycbcrImage.CrPixels[co + (x / scale)]; + + TColor packed = default(TColor); + packed.PackFromBytes(red, green, blue, 255); + pixels[x, y] = packed; + } + }); + } + + this.AssignResolution(image); + } /// - /// Undoes the most recent ReadByteStuffedByte call, - /// giving a byte of data back from bits to bytes. The Huffman look-up table - /// requires at least 8 bits for look-up, which means that Huffman decoding can - /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot - /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte. + /// Converts the image from the original YCbCr image pixels. /// - private void UnreadByteStuffedByte() + /// The pixel format. + /// The image width. + /// The image height. + /// The image. + private void ConvertFromYCbCr(int width, int height, Image image) + where TColor : struct, IPackedPixel, IEquatable { - this.bytes.I -= this.bytes.UnreadableBytes; - this.bytes.UnreadableBytes = 0; - if (this.bits.UnreadBits >= 8) + int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + image.InitPixels(width, height); + + using (PixelAccessor pixels = image.Lock()) { - this.bits.Accumulator >>= 8; - this.bits.UnreadBits -= 8; - this.bits.Mask >>= 8; + Parallel.For( + 0, + height, + Bootstrapper.ParallelOptions, + y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) + { + byte yy = this.ycbcrImage.YPixels[yo + x]; + byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; + + TColor packed = default(TColor); + PackYcbCr(ref packed, yy, cb, cr); + pixels[x, y] = packed; + } + }); } + + this.AssignResolution(image); } /// - /// Reads exactly length bytes into data. It does not care about byte stuffing. + /// Converts the image from the original YCCK image pixels. /// - /// The data to write to. - /// The offset in the source buffer - /// The number of bytes to read - internal void ReadFull(byte[] data, int offset, int length) + /// The pixel format. + /// The image width. + /// The image height. + /// The image. + private void ConvertFromYcck(int width, int height, Image image) + where TColor : struct, IPackedPixel, IEquatable { - // Unread the overshot bytes, if any. - if (this.bytes.UnreadableBytes != 0) - { - if (this.bits.UnreadBits >= 8) - { - this.UnreadByteStuffedByte(); - } + int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; - this.bytes.UnreadableBytes = 0; - } + image.InitPixels(width, height); - while (length > 0) + using (PixelAccessor pixels = image.Lock()) { - if (this.bytes.J - this.bytes.I >= length) - { - Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, length); - this.bytes.I += length; - length -= length; - } - else - { - Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, this.bytes.J - this.bytes.I); - offset += this.bytes.J - this.bytes.I; - length -= this.bytes.J - this.bytes.I; - this.bytes.I += this.bytes.J - this.bytes.I; + Parallel.For( + 0, + height, + y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); - this.bytes.Fill(this.inputStream); - } + for (int x = 0; x < width; x++) + { + byte yy = this.ycbcrImage.YPixels[yo + x]; + byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; + + TColor packed = default(TColor); + this.PackYcck(ref packed, yy, cb, cr, x, y); + pixels[x, y] = packed; + } + }); } + + this.AssignResolution(image); } /// - /// Skips the next n bytes. + /// Decodes a single bit /// - /// The number of bytes to ignore. - private void Skip(int count) + /// The + private bool DecodeBit() { - // Unread the overshot bytes, if any. - if (this.bytes.UnreadableBytes != 0) + if (this.bits.UnreadBits == 0) { - if (this.bits.UnreadBits >= 8) + ErrorCodes errorCode = this.bits.EnsureNBits(1, this); + if (errorCode != ErrorCodes.NoError) { - this.UnreadByteStuffedByte(); + throw new MissingFF00Exception(); } - - this.bytes.UnreadableBytes = 0; } - while (true) - { - int m = this.bytes.J - this.bytes.I; - if (m > count) - { - m = count; - } + bool ret = (this.bits.Accumulator & this.bits.Mask) != 0; + this.bits.UnreadBits--; + this.bits.Mask >>= 1; + return ret; + } - this.bytes.I += m; - count -= m; - if (count == 0) + /// + /// Decodes the given number of bits + /// + /// The number of bits to decode. + /// The + private uint DecodeBits(int count) + { + if (this.bits.UnreadBits < count) + { + ErrorCodes errorCode = this.bits.EnsureNBits(count, this); + if (errorCode != ErrorCodes.NoError) { - break; + throw new MissingFF00Exception(); } - - this.bytes.Fill(this.inputStream); } + + uint ret = this.bits.Accumulator >> (this.bits.UnreadBits - count); + ret = (uint)(ret & ((1 << count) - 1)); + this.bits.UnreadBits -= count; + this.bits.Mask >>= count; + return ret; } /// - /// Processes the Start of Frame marker. Specified in section B.2.2. + /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. /// - /// The remaining bytes in the segment block. - private void ProcessStartOfFrameMarker(int remaining) + /// The huffman value + /// The + private byte DecodeHuffman(ref HuffmanTree huffmanTree) { - if (this.componentCount != 0) + // Copy stuff to the stack: + if (huffmanTree.Length == 0) { - throw new ImageFormatException("Multiple SOF markers"); + throw new ImageFormatException("Uninitialized Huffman table"); } - switch (remaining) + if (this.bits.UnreadBits < 8) { - case 6 + (3 * 1): // Grayscale image. - this.componentCount = 1; - break; - case 6 + (3 * 3): // YCbCr or RGB image. - this.componentCount = 3; - break; - case 6 + (3 * 4): // YCbCrK or CMYK image. - this.componentCount = 4; - break; - default: - throw new ImageFormatException("Incorrect number of components"); - } - - this.ReadFull(this.temp, 0, remaining); + ErrorCodes errorCode = this.bits.EnsureNBits(8, this); - // We only support 8-bit precision. - if (this.temp[0] != 8) - { - throw new ImageFormatException("Only 8-Bit precision supported."); - } + if (errorCode == ErrorCodes.NoError) + { + ushort v = + huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - HuffmanTree.LutSize)) & 0xff]; - this.imageHeight = (this.temp[1] << 8) + this.temp[2]; - this.imageWidth = (this.temp[3] << 8) + this.temp[4]; - if (this.temp[5] != this.componentCount) - { - throw new ImageFormatException("SOF has wrong length"); + if (v != 0) + { + byte n = (byte)((v & 0xff) - 1); + this.bits.UnreadBits -= n; + this.bits.Mask >>= n; + return (byte)(v >> 8); + } + } + else + { + this.UnreadByteStuffedByte(); + } } - for (int i = 0; i < this.componentCount; i++) + int code = 0; + for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) { - this.componentArray[i].Identifier = this.temp[6 + (3 * i)]; - - // Section B.2.2 states that "the value of C_i shall be different from - // the values of C_1 through C_(i-1)". - for (int j = 0; j < i; j++) + if (this.bits.UnreadBits == 0) { - if (this.componentArray[i].Identifier == this.componentArray[j].Identifier) + ErrorCodes errorCode = this.bits.EnsureNBits(1, this); + if (errorCode != ErrorCodes.NoError) { - throw new ImageFormatException("Repeated component identifier"); + throw new MissingFF00Exception(); } } - this.componentArray[i].Selector = this.temp[8 + (3 * i)]; - if (this.componentArray[i].Selector > MaxTq) + if ((this.bits.Accumulator & this.bits.Mask) != 0) { - throw new ImageFormatException("Bad Tq value"); + code |= 1; } - byte hv = this.temp[7 + (3 * i)]; - int h = hv >> 4; - int v = hv & 0x0f; - if (h < 1 || h > 4 || v < 1 || v > 4) - { - throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio"); - } + this.bits.UnreadBits--; + this.bits.Mask >>= 1; - if (h == 3 || v == 3) + if (code <= huffmanTree.MaxCodes[i]) { - throw new ImageFormatException("Lnsupported subsampling ratio"); + return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]]; } - switch (this.componentCount) - { - case 1: + code <<= 1; + } - // If a JPEG image has only one component, section A.2 says "this data - // is non-interleaved by definition" and section A.2.2 says "[in this - // case...] the order of data units within a scan shall be left-to-right - // and top-to-bottom... regardless of the values of H_1 and V_1". Section - // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be - // one data unit". Similarly, section A.1.1 explains that it is the ratio - // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale - // images, H_1 is the maximum H_j for all components j, so that ratio is - // always 1. The component's (h, v) is effectively always (1, 1): even if - // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8 - // MCUs, not two 16x8 MCUs. - h = 1; - v = 1; + throw new ImageFormatException("Bad Huffman code"); + } + + private JpegPixelArea GetDestinationChannel(int compIndex) + { + if (this.componentCount == 1) + { + return this.grayImage; + } + else + { + switch (compIndex) + { + case 0: + return this.ycbcrImage.YChannel; + case 1: + return this.ycbcrImage.CbChannel; + case 2: + return this.ycbcrImage.CrChannel; + case 3: + return this.blackImage; + default: + throw new ImageFormatException("Too many components"); + } + } + } + + /// + /// Returns a value indicating whether the image in an RGB image. + /// + /// + /// The . + /// + private bool IsRGB() + { + if (this.isJfif) + { + return false; + } + + if (this.adobeTransformValid && this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) + { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr. + return true; + } + + return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G' + && this.componentArray[2].Identifier == 'B'; + } + + /// + /// Makes the image from the buffer. + /// + /// The horizontal MCU count + /// The vertical MCU count + private void MakeImage(int mxx, int myy) + { + if (this.grayImage.IsInitialized || this.ycbcrImage != null) + { + return; + } + + if (this.componentCount == 1) + { + this.grayImage = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy); + } + else + { + int h0 = this.componentArray[0].HorizontalFactor; + int v0 = this.componentArray[0].VerticalFactor; + int horizontalRatio = h0 / this.componentArray[1].HorizontalFactor; + int verticalRatio = v0 / this.componentArray[1].VerticalFactor; + + YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; + switch ((horizontalRatio << 4) | verticalRatio) + { + case 0x11: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; + break; + case 0x12: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440; + break; + case 0x21: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422; + break; + case 0x22: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420; + break; + case 0x41: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411; break; + case 0x42: + ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410; + break; + } + + this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio); + + if (this.componentCount == 4) + { + int h3 = this.componentArray[3].HorizontalFactor; + int v3 = this.componentArray[3].VerticalFactor; + + this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * mxx, 8 * v3 * myy); + } + } + } + + /// + /// Optimized method to pack bytes to the image from the CMYK color space. + /// This is faster than implicit casting as it avoids double packing. + /// + /// The pixel format. + /// The packed pixel. + /// The cyan component. + /// The magenta component. + /// The yellow component. + /// The x-position within the image. + /// The y-position within the image. + private void PackCmyk(ref TColor packed, byte c, byte m, byte y, int xx, int yy) + where TColor : struct, IPackedPixel, IEquatable + { + // Get keyline + float keyline = (255 - this.blackImage[xx, yy]) / 255F; + + // Convert back to RGB. CMY are not inverted + byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255); + byte g = (byte)(((m / 255F) * (1F - keyline)).Clamp(0, 1) * 255); + byte b = (byte)(((y / 255F) * (1F - keyline)).Clamp(0, 1) * 255); + + packed.PackFromBytes(r, g, b, 255); + } + + /// + /// Optimized method to pack bytes to the image from the YCCK color space. + /// This is faster than implicit casting as it avoids double packing. + /// + /// The pixel format. + /// The packed pixel. + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + /// The x-position within the image. + /// The y-position within the image. + private void PackYcck(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy) + where TColor : struct, IPackedPixel, IEquatable + { + // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get + // CMY, and patch in the original K. The RGB to CMY inversion cancels + // out the 'Adobe inversion' described in the applyBlack doc comment + // above, so in practice, only the fourth channel (black) is inverted. + // TODO: We can speed this up further with Vector4 + int ccb = cb - 128; + int ccr = cr - 128; + + // First convert from YCbCr to CMY + float cyan = (y + (1.402F * ccr)).Clamp(0, 255) / 255F; + float magenta = (y - (0.34414F * ccb) - (0.71414F * ccr)).Clamp(0, 255) / 255F; + float yellow = (y + (1.772F * ccb)).Clamp(0, 255) / 255F; + + // Get keyline + float keyline = (255 - this.blackImage[xx, yy]) / 255F; + + // Convert back to RGB + byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255); + byte g = (byte)(((1 - magenta) * (1 - keyline)).Clamp(0, 1) * 255); + byte b = (byte)(((1 - yellow) * (1 - keyline)).Clamp(0, 1) * 255); + + packed.PackFromBytes(r, g, b, 255); + } + + /// + /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters. + /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not + /// deleted by default when deleting all metadata because it may affect the appearance of the image. + /// + /// The remaining number of bytes in the stream. + private void ProcessApp14Marker(int remaining) + { + if (remaining < 12) + { + this.Skip(remaining); + return; + } + + this.ReadFull(this.temp, 0, 12); + remaining -= 12; + + if (this.temp[0] == 'A' && this.temp[1] == 'd' && this.temp[2] == 'o' && this.temp[3] == 'b' + && this.temp[4] == 'e') + { + this.adobeTransformValid = true; + this.adobeTransform = this.temp[11]; + } + + if (remaining > 0) + { + this.Skip(remaining); + } + } + + /// + /// Processes the App1 marker retrieving any stored metadata + /// + /// The pixel format. + /// The remaining bytes in the segment block. + /// The image. + private void ProcessApp1Marker(int remaining, Image image) + where TColor : struct, IPackedPixel, IEquatable + { + if (remaining < 6) + { + this.Skip(remaining); + return; + } + + byte[] profile = new byte[remaining]; + this.ReadFull(profile, 0, remaining); + + if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' + && profile[5] == '\0') + { + image.ExifProfile = new ExifProfile(profile); + } + } + + /// + /// Processes the application header containing the JFIF identifier plus extra data. + /// + /// The remaining bytes in the segment block. + private void ProcessApplicationHeader(int remaining) + { + if (remaining < 5) + { + this.Skip(remaining); + return; + } + + this.ReadFull(this.temp, 0, 13); + remaining -= 13; + + // TODO: We should be using constants for this. + this.isJfif = this.temp[0] == 'J' && this.temp[1] == 'F' && this.temp[2] == 'I' && this.temp[3] == 'F' + && this.temp[4] == '\x00'; + + if (this.isJfif) + { + this.horizontalResolution = (short)(this.temp[9] + (this.temp[10] << 8)); + this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8)); + } + + if (remaining > 0) + { + this.Skip(remaining); + } + } + + private void ProcessBlockImpl( + int ah, + Block8x8F* b, + Block8x8F* temp1, + Block8x8F* temp2, + int* unzigPtr, + Scan[] scan, + int i, + int zigStart, + int zigEnd, + int al, + int[] dc, + int compIndex, + int @by, + int mxx, + int hi, + int bx, + Block8x8F* qt) + { + int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector; + if (ah != 0) + { + this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al); + } + else + { + int zig = zigStart; + if (zig == 0) + { + zig++; + + // Decode the DC coefficient, as specified in section F.2.2.1. + byte value = + this.DecodeHuffman( + ref this.huffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); + if (value > 16) + { + throw new ImageFormatException("Excessive DC component"); + } + + int deltaDC = this.bits.ReceiveExtend(value, this); + dc[compIndex] += deltaDC; + + // b[0] = dc[compIndex] << al; + Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al); + } + + if (zig <= zigEnd && this.eobRun > 0) + { + this.eobRun--; + } + else + { + // Decode the AC coefficients, as specified in section F.2.2.2. + // Huffman huffv = ; + for (; zig <= zigEnd; zig++) + { + byte value = this.DecodeHuffman(ref this.huffmanTrees[huffmannIdx]); + byte val0 = (byte)(value >> 4); + byte val1 = (byte)(value & 0x0f); + if (val1 != 0) + { + zig += val0; + if (zig > zigEnd) + { + break; + } - case 3: + int ac = this.bits.ReceiveExtend(val1, this); - // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0, - // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the - // (h, v) values for the Y component are either (1, 1), (1, 2), - // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values - // must be a multiple of the Cb and Cr component's values. We also - // assume that the two chroma components have the same subsampling - // ratio. - switch (i) + // b[Unzig[zig]] = ac << al; + Block8x8F.SetScalarAt(b, unzigPtr[zig], ac << al); + } + else { - case 0: + if (val0 != 0x0f) + { + this.eobRun = (ushort)(1 << val0); + if (val0 != 0) { - // Y. - // We have already verified, above, that h and v are both - // either 1, 2 or 4, so invalid (h, v) combinations are those - // with v == 4. - if (v == 4) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } - - break; + this.eobRun |= (ushort)this.DecodeBits(val0); } - case 1: - { - // Cb. - if (this.componentArray[0].HorizontalFactor % h != 0 - || this.componentArray[0].VerticalFactor % v != 0) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } + this.eobRun--; + break; + } - break; - } + zig += 0x0f; + } + } + } + } - case 2: - { - // Cr. - if (this.componentArray[1].HorizontalFactor != h - || this.componentArray[1].VerticalFactor != v) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } + if (this.isProgressive) + { + if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0) + { + // We haven't completely decoded this 8x8 block. Save the coefficients. - break; - } - } + // TODO!!! + // throw new NotImplementedException(); + // this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone(); + this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = *b; - break; + // At this point, we could execute the rest of the loop body to dequantize and + // perform the inverse DCT, to save early stages of a progressive image to the + // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, + // the jpeg.Decode function does not return until the entire image is decoded, + // so we "continue" here to avoid wasted computation. + return; + } + } - case 4: + // Dequantize, perform the inverse DCT and store the block to the image. + Block8x8F.UnZig(b, qt, unzigPtr); - // For 4-component images (either CMYK or YCbCrK), we only support two - // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. - // Theoretically, 4-component JPEG images could mix and match hv values - // but in practice, those two combinations are the only ones in use, - // and it simplifies the applyBlack code below if we can assume that: - // - for CMYK, the C and K channels have full samples, and if the M - // and Y channels subsample, they subsample both horizontally and - // vertically. - // - for YCbCrK, the Y and K channels have full samples. - switch (i) - { - case 0: - if (hv != 0x11 && hv != 0x22) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } + DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2); - break; - case 1: - case 2: - if (hv != 0x11) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } + var destChannel = this.GetDestinationChannel(compIndex); + var destArea = destChannel.GetOffsetedSubAreaForBlock(bx, by); + destArea.LoadColorsFrom(temp1, temp2); + } - break; - case 3: - if (this.componentArray[0].HorizontalFactor != h - || this.componentArray[0].VerticalFactor != v) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } + private void ProcessComponentImpl( + int i, + ref Scan currentScan, + Scan[] scan, + ref int totalHv, + ref Component currentComponent) + { + // Section B.2.3 states that "the value of Cs_j shall be different from + // the values of Cs_1 through Cs_(j-1)". Since we have previously + // verified that a frame's component identifiers (C_i values in section + // B.2.2) are unique, it suffices to check that the implicit indexes + // into comp are unique. + for (int j = 0; j < i; j++) + { + if (currentScan.Index == scan[j].Index) + { + throw new ImageFormatException("Repeated component selector"); + } + } - break; - } + totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor; - break; + currentScan.DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); + if (currentScan.DcTableSelector > HuffmanTree.MaxTh) + { + throw new ImageFormatException("Bad DC table selector value"); + } + + currentScan.AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); + if (currentScan.AcTableSelector > HuffmanTree.MaxTh) + { + throw new ImageFormatException("Bad AC table selector value"); + } + } + + /// + /// Processes a Define Huffman Table marker, and initializes a huffman + /// struct from its contents. Specified in section B.2.4.2. + /// + /// The remaining bytes in the segment block. + private void ProcessDefineHuffmanTablesMarker(int remaining) + { + while (remaining > 0) + { + if (remaining < 17) + { + throw new ImageFormatException("DHT has wrong length"); } - this.componentArray[i].HorizontalFactor = h; - this.componentArray[i].VerticalFactor = v; + this.ReadFull(this.temp, 0, 17); + + int tc = this.temp[0] >> 4; + if (tc > HuffmanTree.MaxTc) + { + throw new ImageFormatException("Bad Tc value"); + } + + int th = this.temp[0] & 0x0f; + if (th > HuffmanTree.MaxTh || (!this.isProgressive && (th > 1))) + { + throw new ImageFormatException("Bad Th value"); + } + + int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th; + this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.temp, ref remaining); + } + } + + /// + /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in + /// macroblocks + /// + /// The remaining bytes in the segment block. + private void ProcessDefineRestartIntervalMarker(int remaining) + { + if (remaining != 2) + { + throw new ImageFormatException("DRI has wrong length"); } + + this.ReadFull(this.temp, 0, 2); + this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1]; } /// - /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. + /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. /// /// The remaining bytes in the segment block. /// - /// Thrown if the tables do not match the header + /// Thrown if the tables do not match the header /// private void ProcessDqt(int remaining) { @@ -973,327 +1367,231 @@ namespace ImageSharp.Formats } } - /// - /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in macroblocks - /// - /// The remaining bytes in the segment block. - private void ProcessDefineRestartIntervalMarker(int remaining) - { - if (remaining != 2) - { - throw new ImageFormatException("DRI has wrong length"); - } - - this.ReadFull(this.temp, 0, 2); - this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1]; - } - - /// - /// Processes the application header containing the JFIF identifier plus extra data. - /// - /// The remaining bytes in the segment block. - private void ProcessApplicationHeader(int remaining) - { - if (remaining < 5) - { - this.Skip(remaining); - return; - } - - this.ReadFull(this.temp, 0, 13); - remaining -= 13; - - // TODO: We should be using constants for this. - this.isJfif = this.temp[0] == 'J' && this.temp[1] == 'F' && this.temp[2] == 'I' && this.temp[3] == 'F' - && this.temp[4] == '\x00'; - - if (this.isJfif) - { - this.horizontalResolution = (short)(this.temp[9] + (this.temp[10] << 8)); - this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8)); - } - - if (remaining > 0) - { - this.Skip(remaining); - } - } - - /// - /// Processes the App1 marker retrieving any stored metadata - /// - /// The pixel format. - /// The remaining bytes in the segment block. - /// The image. - private void ProcessApp1Marker(int remaining, Image image) - where TColor : struct, IPackedPixel, IEquatable + private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv) { - if (remaining < 6) + // Component selector. + int cs = this.temp[1 + (2 * i)]; + int compIndex = -1; + for (int j = 0; j < this.componentCount; j++) { - this.Skip(remaining); - return; + // Component compv = ; + if (cs == this.componentArray[j].Identifier) + { + compIndex = j; + } } - byte[] profile = new byte[remaining]; - this.ReadFull(profile, 0, remaining); - - if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' - && profile[5] == '\0') + if (compIndex < 0) { - image.ExifProfile = new ExifProfile(profile); + throw new ImageFormatException("Unknown component selector"); } + + currentScan.Index = (byte)compIndex; + + this.ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]); } /// - /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters. - /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not - /// deleted by default when deleting all metadata because it may affect the appearance of the image. + /// Processes the Start of Frame marker. Specified in section B.2.2. /// - /// The remaining number of bytes in the stream. - private void ProcessApp14Marker(int remaining) + /// The remaining bytes in the segment block. + private void ProcessStartOfFrameMarker(int remaining) { - if (remaining < 12) + if (this.componentCount != 0) { - this.Skip(remaining); - return; + throw new ImageFormatException("Multiple SOF markers"); } - this.ReadFull(this.temp, 0, 12); - remaining -= 12; - - if (this.temp[0] == 'A' && this.temp[1] == 'd' && this.temp[2] == 'o' && this.temp[3] == 'b' - && this.temp[4] == 'e') + switch (remaining) { - this.adobeTransformValid = true; - this.adobeTransform = this.temp[11]; + case 6 + (3 * 1): // Grayscale image. + this.componentCount = 1; + break; + case 6 + (3 * 3): // YCbCr or RGB image. + this.componentCount = 3; + break; + case 6 + (3 * 4): // YCbCrK or CMYK image. + this.componentCount = 4; + break; + default: + throw new ImageFormatException("Incorrect number of components"); } - if (remaining > 0) + this.ReadFull(this.temp, 0, remaining); + + // We only support 8-bit precision. + if (this.temp[0] != 8) { - this.Skip(remaining); + throw new ImageFormatException("Only 8-Bit precision supported."); } - } - /// - /// Converts the image from the original YCCK image pixels. - /// - /// The pixel format. - /// The image width. - /// The image height. - /// The image. - private void ConvertFromYcck(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable - { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; - - image.InitPixels(width, height); - - using (PixelAccessor pixels = image.Lock()) + this.imageHeight = (this.temp[1] << 8) + this.temp[2]; + this.imageWidth = (this.temp[3] << 8) + this.temp[4]; + if (this.temp[5] != this.componentCount) { - Parallel.For( - 0, - height, - y => - { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < width; x++) - { - byte yy = this.ycbcrImage.YPixels[yo + x]; - byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; - - TColor packed = default(TColor); - this.PackYcck(ref packed, yy, cb, cr, x, y); - pixels[x, y] = packed; - } - }); + throw new ImageFormatException("SOF has wrong length"); } - this.AssignResolution(image); - } - - /// - /// Converts the image from the original CMYK image pixels. - /// - /// The pixel format. - /// The image width. - /// The image height. - /// The image. - private void ConvertFromCmyk(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable - { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; - - image.InitPixels(width, height); - - using (PixelAccessor pixels = image.Lock()) + for (int i = 0; i < this.componentCount; i++) { - Parallel.For( - 0, - height, - y => + this.componentArray[i].Identifier = this.temp[6 + (3 * i)]; + + // Section B.2.2 states that "the value of C_i shall be different from + // the values of C_1 through C_(i-1)". + for (int j = 0; j < i; j++) + { + if (this.componentArray[i].Identifier == this.componentArray[j].Identifier) { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); + throw new ImageFormatException("Repeated component identifier"); + } + } - for (int x = 0; x < width; x++) - { - byte cyan = this.ycbcrImage.YPixels[yo + x]; - byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)]; + this.componentArray[i].Selector = this.temp[8 + (3 * i)]; + if (this.componentArray[i].Selector > MaxTq) + { + throw new ImageFormatException("Bad Tq value"); + } - TColor packed = default(TColor); - this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); - pixels[x, y] = packed; - } - }); - } + byte hv = this.temp[7 + (3 * i)]; + int h = hv >> 4; + int v = hv & 0x0f; + if (h < 1 || h > 4 || v < 1 || v > 4) + { + throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio"); + } - this.AssignResolution(image); - } + if (h == 3 || v == 3) + { + throw new ImageFormatException("Lnsupported subsampling ratio"); + } - /// - /// Converts the image from the original grayscale image pixels. - /// - /// The pixel format. - /// The image width. - /// The image height. - /// The image. - private void ConvertFromGrayScale(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable - { - image.InitPixels(width, height); + switch (this.componentCount) + { + case 1: - using (PixelAccessor pixels = image.Lock()) - { - Parallel.For( - 0, - height, - Bootstrapper.ParallelOptions, - y => - { - int yoff = this.grayImage.GetRowOffset(y); - for (int x = 0; x < width; x++) - { - byte rgb = this.grayImage.Pixels[yoff + x]; + // If a JPEG image has only one component, section A.2 says "this data + // is non-interleaved by definition" and section A.2.2 says "[in this + // case...] the order of data units within a scan shall be left-to-right + // and top-to-bottom... regardless of the values of H_1 and V_1". Section + // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be + // one data unit". Similarly, section A.1.1 explains that it is the ratio + // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale + // images, H_1 is the maximum H_j for all components j, so that ratio is + // always 1. The component's (h, v) is effectively always (1, 1): even if + // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8 + // MCUs, not two 16x8 MCUs. + h = 1; + v = 1; + break; - TColor packed = default(TColor); - packed.PackFromBytes(rgb, rgb, rgb, 255); - pixels[x, y] = packed; - } - }); - } + case 3: - this.AssignResolution(image); - } + // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0, + // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the + // (h, v) values for the Y component are either (1, 1), (1, 2), + // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values + // must be a multiple of the Cb and Cr component's values. We also + // assume that the two chroma components have the same subsampling + // ratio. + switch (i) + { + case 0: + { + // Y. + // We have already verified, above, that h and v are both + // either 1, 2 or 4, so invalid (h, v) combinations are those + // with v == 4. + if (v == 4) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } - /// - /// Converts the image from the original YCbCr image pixels. - /// - /// The pixel format. - /// The image width. - /// The image height. - /// The image. - private void ConvertFromYCbCr(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable - { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; - image.InitPixels(width, height); + break; + } - using (PixelAccessor pixels = image.Lock()) - { - Parallel.For( - 0, - height, - Bootstrapper.ParallelOptions, - y => - { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); + case 1: + { + // Cb. + if (this.componentArray[0].HorizontalFactor % h != 0 + || this.componentArray[0].VerticalFactor % v != 0) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } - for (int x = 0; x < width; x++) - { - byte yy = this.ycbcrImage.YPixels[yo + x]; - byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; + break; + } - TColor packed = default(TColor); - PackYcbCr(ref packed, yy, cb, cr); - pixels[x, y] = packed; - } - }); - } + case 2: + { + // Cr. + if (this.componentArray[1].HorizontalFactor != h + || this.componentArray[1].VerticalFactor != v) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } - this.AssignResolution(image); - } + break; + } + } - /// - /// Converts the image from the original RBG image pixels. - /// - /// The pixel format. - /// The image width. - /// The height. - /// The image. - private void ConvertFromRGB(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable - { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; - image.InitPixels(width, height); + break; - using (PixelAccessor pixels = image.Lock()) - { - Parallel.For( - 0, - height, - Bootstrapper.ParallelOptions, - y => + case 4: + + // For 4-component images (either CMYK or YCbCrK), we only support two + // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. + // Theoretically, 4-component JPEG images could mix and match hv values + // but in practice, those two combinations are the only ones in use, + // and it simplifies the applyBlack code below if we can assume that: + // - for CMYK, the C and K channels have full samples, and if the M + // and Y channels subsample, they subsample both horizontally and + // vertically. + // - for YCbCrK, the Y and K channels have full samples. + switch (i) { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); + case 0: + if (hv != 0x11 && hv != 0x22) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } - for (int x = 0; x < width; x++) - { - byte red = this.ycbcrImage.YPixels[yo + x]; - byte green = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte blue = this.ycbcrImage.CrPixels[co + (x / scale)]; + break; + case 1: + case 2: + if (hv != 0x11) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } - TColor packed = default(TColor); - packed.PackFromBytes(red, green, blue, 255); - pixels[x, y] = packed; - } - }); - } + break; + case 3: + if (this.componentArray[0].HorizontalFactor != h + || this.componentArray[0].VerticalFactor != v) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } - this.AssignResolution(image); - } + break; + } - /// - /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. - /// - /// The pixel format. - /// The image to assign the resolution to. - private void AssignResolution(Image image) - where TColor : struct, IPackedPixel, IEquatable - { - if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) - { - image.HorizontalResolution = this.horizontalResolution; - image.VerticalResolution = this.verticalResolution; + break; + } + + this.componentArray[i].HorizontalFactor = h; + this.componentArray[i].VerticalFactor = v; } } /// - /// Processes the SOS (Start of scan marker). + /// Processes the SOS (Start of scan marker). /// /// - /// TODO: This also needs some significant refactoring to follow a more OO format. + /// TODO: This also needs some significant refactoring to follow a more OO format. /// /// The remaining bytes in the segment block. /// - /// Missing SOF Marker - /// SOS has wrong length + /// Missing SOF Marker + /// SOS has wrong length /// private void ProcessStartOfScan(int remaining) { @@ -1378,7 +1676,7 @@ namespace ImageSharp.Formats int v0 = this.componentArray[0].VerticalFactor; int mxx = (this.imageWidth + (8 * h0) - 1) / (8 * h0); int myy = (this.imageHeight + (8 * v0) - 1) / (8 * v0); - + this.MakeImage(mxx, myy); if (this.isProgressive) @@ -1410,7 +1708,6 @@ namespace ImageSharp.Formats int bx, by, blockCount = 0; // TODO: A DecoderScanProcessor struct could clean up this mess - Block8x8F b = default(Block8x8F); Block8x8F temp1 = default(Block8x8F); Block8x8F temp2 = default(Block8x8F); @@ -1473,7 +1770,7 @@ namespace ImageSharp.Formats int qtIndex = this.componentArray[compIndex].Selector; // TODO: A DecoderScanProcessor struct could clean up this mess - // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. (The first one could be async) + // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex]) { // Load the previous partially decoded coefficients, if applicable. @@ -1516,264 +1813,59 @@ namespace ImageSharp.Formats i, zigStart, zigEnd, - al, - dc, - compIndex, - @by, - mxx, - hi, - bx, - qtp); - } - } - } - - // for j - } - - // for i - mcu++; - - if (this.restartInterval > 0 && mcu % this.restartInterval == 0 && mcu < mxx * myy) - { - // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, - // but this one assumes well-formed input, and hence the restart marker follows immediately. - this.ReadFull(this.temp, 0, 2); - if (this.temp[0] != 0xff || this.temp[1] != expectedRst) - { - throw new ImageFormatException("Bad RST marker"); - } - - expectedRst++; - if (expectedRst == JpegConstants.Markers.RST7 + 1) - { - expectedRst = JpegConstants.Markers.RST0; - } - - // Reset the Huffman decoder. - this.bits = default(Bits); - - // Reset the DC components, as per section F.2.1.3.1. - dc = new int[MaxComponents]; - - // Reset the progressive decoder state, as per section G.1.2.2. - this.eobRun = 0; - } - } - - // for mx - } - - // for my - } - - private void ProcessBlockImpl( - int ah, - Block8x8F* b, - Block8x8F* temp1, - Block8x8F* temp2, - int* unzigPtr, - Scan[] scan, - int i, - int zigStart, - int zigEnd, - int al, - int[] dc, - int compIndex, - int @by, - int mxx, - int hi, - int bx, - Block8x8F* qt) - { - int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector; - if (ah != 0) - { - this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al); - } - else - { - int zig = zigStart; - if (zig == 0) - { - zig++; - - // Decode the DC coefficient, as specified in section F.2.2.1. - byte value = this.DecodeHuffman( - ref this.huffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); - if (value > 16) - { - throw new ImageFormatException("Excessive DC component"); - } - - int deltaDC = this.bits.ReceiveExtend(value, this); - dc[compIndex] += deltaDC; - - // b[0] = dc[compIndex] << al; - Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al); - } - - if (zig <= zigEnd && this.eobRun > 0) - { - this.eobRun--; - } - else - { - // Decode the AC coefficients, as specified in section F.2.2.2. - // Huffman huffv = ; - for (; zig <= zigEnd; zig++) - { - byte value = this.DecodeHuffman(ref this.huffmanTrees[huffmannIdx]); - byte val0 = (byte)(value >> 4); - byte val1 = (byte)(value & 0x0f); - if (val1 != 0) - { - zig += val0; - if (zig > zigEnd) - { - break; - } - - int ac = this.bits.ReceiveExtend(val1, this); - - // b[Unzig[zig]] = ac << al; - Block8x8F.SetScalarAt(b, unzigPtr[zig], ac << al); - } - else - { - if (val0 != 0x0f) - { - this.eobRun = (ushort)(1 << val0); - if (val0 != 0) - { - this.eobRun |= (ushort)this.DecodeBits(val0); - } - - this.eobRun--; - break; - } - - zig += 0x0f; - } - } - } - } - - if (this.isProgressive) - { - if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0) - { - // We haven't completely decoded this 8x8 block. Save the coefficients. - - // TODO!!! - // throw new NotImplementedException(); - // this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone(); - this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = *b; - - // At this point, we could execute the rest of the loop body to dequantize and - // perform the inverse DCT, to save early stages of a progressive image to the - // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, - // the jpeg.Decode function does not return until the entire image is decoded, - // so we "continue" here to avoid wasted computation. - return; - } - } - - // 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); + al, + dc, + compIndex, + @by, + mxx, + hi, + bx, + qtp); + } + } + } - var destChannel = this.GetDestinationChannel(compIndex); - var destArea = destChannel.GetOffsetedAreaForBlock(bx, by); - destArea.LoadColorsFrom(temp1, temp2); - } + // for j + } - private JpegPixelArea GetDestinationChannel(int compIndex) - { - if (this.componentCount == 1) - { - return this.grayImage; - } - else - { - switch (compIndex) - { - case 0: - return this.ycbcrImage.YChannel; - case 1: - return this.ycbcrImage.CbChannel; - case 2: - return this.ycbcrImage.CrChannel; - case 3: - return this.blackImage; - default: - throw new ImageFormatException("Too many components"); - } - } - } + // for i + mcu++; - private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv) - { - // Component selector. - int cs = this.temp[1 + (2 * i)]; - int compIndex = -1; - for (int j = 0; j < this.componentCount; j++) - { - // Component compv = ; - if (cs == this.componentArray[j].Identifier) - { - compIndex = j; - } - } + if (this.restartInterval > 0 && mcu % this.restartInterval == 0 && mcu < mxx * myy) + { + // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, + // but this one assumes well-formed input, and hence the restart marker follows immediately. + this.ReadFull(this.temp, 0, 2); + if (this.temp[0] != 0xff || this.temp[1] != expectedRst) + { + throw new ImageFormatException("Bad RST marker"); + } - if (compIndex < 0) - { - throw new ImageFormatException("Unknown component selector"); - } + expectedRst++; + if (expectedRst == JpegConstants.Markers.RST7 + 1) + { + expectedRst = JpegConstants.Markers.RST0; + } - currentScan.Index = (byte)compIndex; + // Reset the Huffman decoder. + this.bits = default(Bits); - this.ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]); - } + // Reset the DC components, as per section F.2.1.3.1. + dc = new int[MaxComponents]; - private void ProcessComponentImpl( - int i, - ref Scan currentScan, - Scan[] scan, - ref int totalHv, - ref Component currentComponent) - { - // Section B.2.3 states that "the value of Cs_j shall be different from - // the values of Cs_1 through Cs_(j-1)". Since we have previously - // verified that a frame's component identifiers (C_i values in section - // B.2.2) are unique, it suffices to check that the implicit indexes - // into comp are unique. - for (int j = 0; j < i; j++) - { - if (currentScan.Index == scan[j].Index) - { - throw new ImageFormatException("Repeated component selector"); + // Reset the progressive decoder state, as per section G.1.2.2. + this.eobRun = 0; + } } - } - totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor; - - currentScan.DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); - if (currentScan.DcTableSelector > HuffmanTree.MaxTh) - { - throw new ImageFormatException("Bad DC table selector value"); + // for mx } - currentScan.AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); - if (currentScan.AcTableSelector > HuffmanTree.MaxTh) - { - throw new ImageFormatException("Bad AC table selector value"); - } + // for my } /// - /// Decodes a successive approximation refinement block, as specified in section G.1.2. + /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// /// The block of coefficients /// The Huffman tree @@ -1875,16 +1967,16 @@ namespace ImageSharp.Formats } /// - /// Refines non-zero entries of b in zig-zag order. - /// If >= 0, the first zero entries are skipped over. + /// Refines non-zero entries of b in zig-zag order. + /// If >= 0, the first zero entries are skipped over. /// /// The block of coefficients /// The zig-zag start index /// The zig-zag end index /// The non-zero entry /// The low transform offset - /// Pointer to the Jpeg Unzig data (data part of ) - /// The + /// Pointer to the Jpeg Unzig data (data part of ) + /// The private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta, int* unzigPtr) { for (; zig <= zigEnd; zig++) @@ -1924,192 +2016,103 @@ namespace ImageSharp.Formats return zig; } - + /// - /// Makes the image from the buffer. + /// Skips the next n bytes. /// - /// The horizontal MCU count - /// The vertical MCU count - private void MakeImage(int mxx, int myy) + /// The number of bytes to ignore. + private void Skip(int count) { - if (this.grayImage.Created || this.ycbcrImage != null) return; - - if (this.componentCount == 1) + // Unread the overshot bytes, if any. + if (this.bytes.UnreadableBytes != 0) { - this.grayImage = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy); - //this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight); + if (this.bits.UnreadBits >= 8) + { + this.UnreadByteStuffedByte(); + } + + this.bytes.UnreadableBytes = 0; } - else - { - int h0 = this.componentArray[0].HorizontalFactor; - int v0 = this.componentArray[0].VerticalFactor; - int horizontalRatio = h0 / this.componentArray[1].HorizontalFactor; - int verticalRatio = v0 / this.componentArray[1].VerticalFactor; - YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; - switch ((horizontalRatio << 4) | verticalRatio) + while (true) + { + int m = this.bytes.J - this.bytes.I; + if (m > count) { - case 0x11: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; - break; - case 0x12: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440; - break; - case 0x21: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422; - break; - case 0x22: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420; - break; - case 0x41: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411; - break; - case 0x42: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410; - break; + m = count; } - this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio, 0, 0); - - if (this.componentCount == 4) + this.bytes.I += m; + count -= m; + if (count == 0) { - int h3 = this.componentArray[3].HorizontalFactor; - int v3 = this.componentArray[3].VerticalFactor; - - this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * mxx, 8 * v3 * myy); - + break; } + + this.bytes.Fill(this.inputStream); } } /// - /// Returns a value indicating whether the image in an RGB image. + /// Undoes the most recent ReadByteStuffedByte call, + /// giving a byte of data back from bits to bytes. The Huffman look-up table + /// requires at least 8 bits for look-up, which means that Huffman decoding can + /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot + /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte. /// - /// - /// The . - /// - private bool IsRGB() + private void UnreadByteStuffedByte() { - if (this.isJfif) - { - return false; - } - - if (this.adobeTransformValid && this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) + this.bytes.I -= this.bytes.UnreadableBytes; + this.bytes.UnreadableBytes = 0; + if (this.bits.UnreadBits >= 8) { - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr. - return true; + this.bits.Accumulator >>= 8; + this.bits.UnreadBits -= 8; + this.bits.Mask >>= 8; } - - return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G' - && this.componentArray[2].Identifier == 'B'; - } - - /// - /// Optimized method to pack bytes to the image from the YCCK color space. - /// This is faster than implicit casting as it avoids double packing. - /// - /// The pixel format. - /// The packed pixel. - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - /// The x-position within the image. - /// The y-position within the image. - private void PackYcck(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy) - where TColor : struct, IPackedPixel, IEquatable - { - // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get - // CMY, and patch in the original K. The RGB to CMY inversion cancels - // out the 'Adobe inversion' described in the applyBlack doc comment - // above, so in practice, only the fourth channel (black) is inverted. - // TODO: We can speed this up further with Vector4 - int ccb = cb - 128; - int ccr = cr - 128; - - // First convert from YCbCr to CMY - float cyan = (y + (1.402F * ccr)).Clamp(0, 255) / 255F; - float magenta = (y - (0.34414F * ccb) - (0.71414F * ccr)).Clamp(0, 255) / 255F; - float yellow = (y + (1.772F * ccb)).Clamp(0, 255) / 255F; - - // Get keyline - float keyline = (255 - this.blackImage[xx, yy]) / 255F; - - // Convert back to RGB - byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255); - byte g = (byte)(((1 - magenta) * (1 - keyline)).Clamp(0, 1) * 255); - byte b = (byte)(((1 - yellow) * (1 - keyline)).Clamp(0, 1) * 255); - - packed.PackFromBytes(r, g, b, 255); - } - - /// - /// Optimized method to pack bytes to the image from the CMYK color space. - /// This is faster than implicit casting as it avoids double packing. - /// - /// The pixel format. - /// The packed pixel. - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The x-position within the image. - /// The y-position within the image. - private void PackCmyk(ref TColor packed, byte c, byte m, byte y, int xx, int yy) - where TColor : struct, IPackedPixel, IEquatable - { - // Get keyline - float keyline = (255 - this.blackImage[xx, yy]) / 255F; - - // Convert back to RGB. CMY are not inverted - byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - byte g = (byte)(((m / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - byte b = (byte)(((y / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - - packed.PackFromBytes(r, g, b, 255); } /// - /// Represents a component scan + /// Represents a component scan /// private struct Scan { /// - /// Gets or sets the component index. + /// Gets or sets the component index. /// public byte Index { get; set; } /// - /// Gets or sets the DC table selector + /// Gets or sets the DC table selector /// public byte DcTableSelector { get; set; } /// - /// Gets or sets the AC table selector + /// Gets or sets the AC table selector /// public byte AcTableSelector { get; set; } } /// - /// The missing ff00 exception. + /// The EOF (End of File exception). + /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker /// - internal class MissingFF00Exception : Exception + internal class EOFException : Exception { } /// - /// The EOF (End of File exception). - /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker + /// The missing ff00 exception. /// - internal class EOFException : Exception + internal class MissingFF00Exception : Exception { } /// - /// The short huffman data exception. + /// The short huffman data exception. /// private class ShortHuffmanDataException : Exception { } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs b/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs index a181eeee0..7369d658f 100644 --- a/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs +++ b/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs @@ -1,13 +1,30 @@ -namespace ImageSharp.Formats +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats { using System.Buffers; + /// + /// Wraps to always provide arrays initialized with default(T) + /// + /// The element type internal class CleanPooler { private static readonly ArrayPool Pool = ArrayPool.Create(); + /// + /// Rents a clean array + /// + /// The minimum array length + /// A clean array of T public static T[] RentCleanArray(int minimumLength) => Pool.Rent(minimumLength); + /// + /// Retursn array to the pool + /// + /// The array public static void ReturnArray(T[] array) => Pool.Return(array, true); } } \ No newline at end of file From ee1400f39b5b9fff72d49b8c354d44e1e39b6260 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Tue, 27 Dec 2016 02:29:55 +0100 Subject: [PATCH 07/24] started work on DecoderScanProcessor --- ImageSharp.sln.DotSettings | 1 + .../Decoder/DecoderScanProcessor.cs | 229 ++++++++++++++ .../Jpg/Components/Decoder/HuffmanTree.cs | 6 +- .../Formats/Jpg/Components/Decoder/Scan.cs | 26 ++ src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 288 +++++++++--------- 5 files changed, 397 insertions(+), 153 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs create mode 100644 src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index 5acec071a..b0f5aa692 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -345,6 +345,7 @@ FDCT IDCT JPEG + MCU PNG RGB RLE diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs new file mode 100644 index 000000000..b0c44c65b --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs @@ -0,0 +1,229 @@ +namespace ImageSharp.Formats.Jpg +{ + using System; + + internal unsafe struct DecoderScanProcessor + { + public struct ComponentData + { + public Block8x8F Block; + + public Block8x8F Temp1; + + public Block8x8F Temp2; + + public UnzigData Unzig; + + public fixed byte ScanData [3 * JpegDecoderCore.MaxComponents]; + + public fixed int Dc [JpegDecoderCore.MaxComponents]; + + public static ComponentData Create() + { + ComponentData data = default(ComponentData); + data.Unzig = UnzigData.Create(); + return data; + } + + } + + public struct ComponentPointers + { + public Block8x8F* Block; + + public Block8x8F* Temp1; + + public Block8x8F* Temp2; + + public int* Unzig; + + public Scan* Scan; + + public int* Dc; + + public ComponentPointers(ComponentData* basePtr) + { + this.Block = &basePtr->Block; + this.Temp1 = &basePtr->Temp1; + this.Temp2 = &basePtr->Temp2; + this.Unzig = basePtr->Unzig.Data; + this.Scan = (Scan*) basePtr->ScanData; + this.Dc = basePtr->Dc; + } + } + + public int bx; + + public int by; + + public int zigStart; + + public int zigEnd; + + public int ah; + + public int al; + + public int mxx; + + public int myy; + + public ComponentData Data; + + public ComponentPointers Pointers; + + public static void Init(DecoderScanProcessor* p, JpegDecoderCore decoder, int remaining) + { + p->Pointers = new ComponentPointers(&p->Data); + + } + + private void InitCommon(JpegDecoderCore decoder, int remaining) + { + if (decoder.ComponentCount == 0) + { + throw new ImageFormatException("Missing SOF marker"); + } + + if (remaining < 6 || 4 + (2 * decoder.ComponentCount) < remaining || remaining % 2 != 0) + { + throw new ImageFormatException("SOS has wrong length"); + } + + decoder.ReadFull(decoder.Temp, 0, remaining); + byte scanComponentCount = decoder.Temp[0]; + + int scanComponentCountX2 = 2 * scanComponentCount; + if (remaining != 4 + scanComponentCountX2) + { + throw new ImageFormatException("SOS length inconsistent with number of components"); + } + + int totalHv = 0; + + for (int i = 0; i < scanComponentCount; i++) + { + this.ProcessScanImpl(decoder, i, ref this.Pointers.Scan[i], ref totalHv); + } + // Section B.2.3 states that if there is more than one component then the + // total H*V values in a scan must be <= 10. + if (decoder.ComponentCount > 1 && totalHv > 10) + { + throw new ImageFormatException("Total sampling factors too large."); + } + + this.zigEnd = Block8x8F.ScalarCount - 1; + + if (decoder.IsProgressive) + { + this.zigStart = decoder.Temp[1 + scanComponentCountX2]; + this.zigEnd = decoder.Temp[2 + scanComponentCountX2]; + this.ah = decoder.Temp[3 + scanComponentCountX2] >> 4; + this.al = decoder.Temp[3 + scanComponentCountX2] & 0x0f; + + if ((this.zigStart == 0 && this.zigEnd != 0) || this.zigStart > this.zigEnd + || this.zigEnd >= Block8x8F.ScalarCount) + { + throw new ImageFormatException("Bad spectral selection bounds"); + } + + if (this.zigStart != 0 && scanComponentCount != 1) + { + throw new ImageFormatException("Progressive AC coefficients for more than one component"); + } + + if (this.ah != 0 && this.ah != this.al + 1) + { + throw new ImageFormatException("Bad successive approximation values"); + } + } + + // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. + int h0 = decoder.ComponentArray[0].HorizontalFactor; + int v0 = decoder.ComponentArray[0].VerticalFactor; + this.mxx = (decoder.ImageWidth + (8 * h0) - 1) / (8 * h0); + this.myy = (decoder.ImageHeight + (8 * v0) - 1) / (8 * v0); + + if (decoder.IsProgressive) + { + for (int i = 0; i < scanComponentCount; i++) + { + int compIndex = this.Pointers.Scan[i].Index; + if (decoder.ProgCoeffs[compIndex] == null) + { + int size = mxx * myy * decoder.ComponentArray[compIndex].HorizontalFactor + * decoder.ComponentArray[compIndex].VerticalFactor; + + decoder.ProgCoeffs[compIndex] = new Block8x8F[size]; + } + } + } + } + + private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref Scan currentScan, ref int totalHv) + { + // Component selector. + int cs = decoder.Temp[1 + (2 * i)]; + int compIndex = -1; + for (int j = 0; j < decoder.ComponentCount; j++) + { + // Component compv = ; + if (cs == decoder.ComponentArray[j].Identifier) + { + compIndex = j; + } + } + + if (compIndex < 0) + { + throw new ImageFormatException("Unknown component selector"); + } + + currentScan.Index = (byte)compIndex; + + this.ProcessComponentImpl(decoder, i, ref currentScan, ref totalHv, ref decoder.ComponentArray[compIndex]); + } + + + private void ProcessComponentImpl( + JpegDecoderCore decoder, + int i, + ref Scan currentScan, + ref int totalHv, + ref Component currentComponent) + { + // Section B.2.3 states that "the value of Cs_j shall be different from + // the values of Cs_1 through Cs_(j-1)". Since we have previously + // verified that a frame's component identifiers (C_i values in section + // B.2.2) are unique, it suffices to check that the implicit indexes + // into comp are unique. + for (int j = 0; j < i; j++) + { + if (currentScan.Index == this.Pointers.Scan[j].Index) + { + throw new ImageFormatException("Repeated component selector"); + } + } + + totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor; + + currentScan.DcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] >> 4); + if (currentScan.DcTableSelector > HuffmanTree.MaxTh) + { + throw new ImageFormatException("Bad DC table selector value"); + } + + currentScan.AcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] & 0x0f); + if (currentScan.AcTableSelector > HuffmanTree.MaxTh) + { + throw new ImageFormatException("Bad AC table selector value"); + } + } + + private void InitProgressive(JpegDecoderCore decoder) + { + + } + } + +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs index 12acdfc6c..bdf3468e6 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs @@ -91,7 +91,7 @@ namespace ImageSharp.Formats.Jpg /// /// Creates and initializes an array of instances of size /// - /// An array of instances representing the Huffman table + /// An array of instances representing the Huffman tables public static HuffmanTree[] CreateHuffmanTrees() { HuffmanTree[] result = new HuffmanTree[NumberOfTrees]; @@ -109,7 +109,7 @@ namespace ImageSharp.Formats.Jpg /// /// Initializes the Huffman tree /// - public void Init() + private void Init() { this.Lut = UshortBuffer.Rent(1 << LutSize); this.Values = ByteBuffer.Rent(MaxNCodes); @@ -134,7 +134,7 @@ namespace ImageSharp.Formats.Jpg /// Internal part of the DHT processor, whatever does it mean /// /// The decoder instance - /// The temporal buffer that holds the data that has been read from stream + /// The temporal buffer that holds the data that has been read from the Jpeg stream /// Remaining bits internal void ProcessDefineHuffmanTablesMarkerLoop( JpegDecoderCore decoder, diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs new file mode 100644 index 000000000..a2f439c5a --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs @@ -0,0 +1,26 @@ +namespace ImageSharp.Formats.Jpg +{ + using System.Runtime.InteropServices; + + /// + /// Represents a component scan + /// + [StructLayout(LayoutKind.Sequential)] + internal struct Scan + { + /// + /// Gets or sets the component index. + /// + public byte Index; + + /// + /// Gets or sets the DC table selector + /// + public byte DcTableSelector; + + /// + /// Gets or sets the AC table selector + /// + public byte AcTableSelector; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 1696d0811..c17a2856e 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats using System.Threading.Tasks; using ImageSharp.Formats.Jpg; - + /// /// Performs the jpeg decoding operation. /// @@ -29,7 +29,7 @@ namespace ImageSharp.Formats /// /// The maximum number of color components /// - private const int MaxComponents = 4; + internal const int MaxComponents = 4; /// /// The maximum number of quantization tables @@ -39,7 +39,7 @@ namespace ImageSharp.Formats /// /// The component array /// - private readonly Component[] componentArray; + internal Component[] ComponentArray { get; } /// /// The huffman trees @@ -49,17 +49,17 @@ namespace ImageSharp.Formats /// /// Saved state between progressive-mode scans. /// - private readonly Block8x8F[][] progCoeffs; + internal Block8x8F[][] ProgCoeffs { get; } /// /// Quantization tables, in zigzag order. /// - private readonly Block8x8F[] quantizationTables; + internal Block8x8F[] QuantizationTables { get; } /// /// A temporary buffer for holding pixels /// - private readonly byte[] temp; + internal byte[] Temp { get; } // TODO: the usage of this buffer is unclean + need to move it to the stack for performance @@ -90,7 +90,7 @@ namespace ImageSharp.Formats /// /// The number of color components within the image. /// - private int componentCount; + internal int ComponentCount { get; private set; } /// /// End-of-Band run, specified in section G.1.2.2. @@ -110,12 +110,12 @@ namespace ImageSharp.Formats /// /// The image height /// - private int imageHeight; + internal int ImageHeight { get; private set; } /// /// The image width /// - private int imageWidth; + internal int ImageWidth { get; private set; } /// /// The byte buffer. @@ -130,7 +130,7 @@ namespace ImageSharp.Formats /// /// Whether the image is interlaced (progressive) /// - private bool isProgressive; + public bool IsProgressive { get; private set; } /// /// The restart interval @@ -153,10 +153,10 @@ namespace ImageSharp.Formats public JpegDecoderCore() { this.huffmanTrees = HuffmanTree.CreateHuffmanTrees(); - this.quantizationTables = new Block8x8F[MaxTq + 1]; - this.temp = new byte[2 * Block8x8F.ScalarCount]; - this.componentArray = new Component[MaxComponents]; - this.progCoeffs = new Block8x8F[MaxComponents][]; + this.QuantizationTables = new Block8x8F[MaxTq + 1]; + this.Temp = new byte[2 * Block8x8F.ScalarCount]; + this.ComponentArray = new Component[MaxComponents]; + this.ProgCoeffs = new Block8x8F[MaxComponents][]; this.bits = default(Bits); this.bytes = Bytes.Create(); } @@ -219,8 +219,8 @@ namespace ImageSharp.Formats this.inputStream = stream; // Check for the Start Of Image marker. - this.ReadFull(this.temp, 0, 2); - if (this.temp[0] != JpegConstants.Markers.XFF || this.temp[1] != JpegConstants.Markers.SOI) + this.ReadFull(this.Temp, 0, 2); + if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI) { throw new ImageFormatException("Missing SOI marker."); } @@ -228,8 +228,8 @@ namespace ImageSharp.Formats // Process the remaining segments until the End Of Image marker. while (true) { - this.ReadFull(this.temp, 0, 2); - while (this.temp[0] != 0xff) + this.ReadFull(this.Temp, 0, 2); + while (this.Temp[0] != 0xff) { // Strictly speaking, this is a format error. However, libjpeg is // liberal in what it accepts. As of version 9, next_marker in @@ -248,11 +248,11 @@ namespace ImageSharp.Formats // mechanism within a scan (the RST[0-7] markers). // Note that extraneous 0xff bytes in e.g. SOS data are escaped as // "\xff\x00", and so are detected a little further down below. - this.temp[0] = this.temp[1]; - this.temp[1] = this.ReadByte(); + this.Temp[0] = this.Temp[1]; + this.Temp[1] = this.ReadByte(); } - byte marker = this.temp[1]; + byte marker = this.Temp[1]; if (marker == 0) { // Treat "\xff\x00" as extraneous data. @@ -285,8 +285,8 @@ namespace ImageSharp.Formats // Read the 16-bit length of the segment. The value includes the 2 bytes for the // length itself, so we subtract 2 to get the number of remaining bytes. - this.ReadFull(this.temp, 0, 2); - int remaining = (this.temp[0] << 8) + this.temp[1] - 2; + this.ReadFull(this.Temp, 0, 2); + int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; if (remaining < 0) { throw new ImageFormatException("Short segment length."); @@ -297,7 +297,7 @@ namespace ImageSharp.Formats case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: - this.isProgressive = marker == JpegConstants.Markers.SOF2; + this.IsProgressive = marker == JpegConstants.Markers.SOF2; this.ProcessStartOfFrameMarker(remaining); if (configOnly && this.isJfif) { @@ -377,11 +377,11 @@ namespace ImageSharp.Formats if (this.grayImage.IsInitialized) { - this.ConvertFromGrayScale(this.imageWidth, this.imageHeight, image); + this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image); } else if (this.ycbcrImage != null) { - if (this.componentCount == 4) + if (this.ComponentCount == 4) { if (!this.adobeTransformValid) { @@ -394,26 +394,26 @@ namespace ImageSharp.Formats // TODO: YCbCrA? if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck) { - this.ConvertFromYcck(this.imageWidth, this.imageHeight, image); + this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image); } else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) { // Assume CMYK - this.ConvertFromCmyk(this.imageWidth, this.imageHeight, image); + this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image); } return; } - if (this.componentCount == 3) + if (this.ComponentCount == 3) { if (this.IsRGB()) { - this.ConvertFromRGB(this.imageWidth, this.imageHeight, image); + this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image); return; } - this.ConvertFromYCbCr(this.imageWidth, this.imageHeight, image); + this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image); return; } @@ -538,7 +538,7 @@ namespace ImageSharp.Formats private void ConvertFromCmyk(int width, int height, Image image) where TColor : struct, IPackedPixel, IEquatable { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); @@ -613,7 +613,7 @@ namespace ImageSharp.Formats private void ConvertFromRGB(int width, int height, Image image) where TColor : struct, IPackedPixel, IEquatable { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); using (PixelAccessor pixels = image.Lock()) @@ -653,7 +653,7 @@ namespace ImageSharp.Formats private void ConvertFromYCbCr(int width, int height, Image image) where TColor : struct, IPackedPixel, IEquatable { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); using (PixelAccessor pixels = image.Lock()) @@ -693,7 +693,7 @@ namespace ImageSharp.Formats private void ConvertFromYcck(int width, int height, Image image) where TColor : struct, IPackedPixel, IEquatable { - int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; + int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); @@ -836,7 +836,7 @@ namespace ImageSharp.Formats private JpegPixelArea GetDestinationChannel(int compIndex) { - if (this.componentCount == 1) + if (this.ComponentCount == 1) { return this.grayImage; } @@ -878,8 +878,8 @@ namespace ImageSharp.Formats return true; } - return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G' - && this.componentArray[2].Identifier == 'B'; + return this.ComponentArray[0].Identifier == 'R' && this.ComponentArray[1].Identifier == 'G' + && this.ComponentArray[2].Identifier == 'B'; } /// @@ -894,16 +894,16 @@ namespace ImageSharp.Formats return; } - if (this.componentCount == 1) + if (this.ComponentCount == 1) { this.grayImage = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy); } else { - int h0 = this.componentArray[0].HorizontalFactor; - int v0 = this.componentArray[0].VerticalFactor; - int horizontalRatio = h0 / this.componentArray[1].HorizontalFactor; - int verticalRatio = v0 / this.componentArray[1].VerticalFactor; + int h0 = this.ComponentArray[0].HorizontalFactor; + int v0 = this.ComponentArray[0].VerticalFactor; + int horizontalRatio = h0 / this.ComponentArray[1].HorizontalFactor; + int verticalRatio = v0 / this.ComponentArray[1].VerticalFactor; YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; switch ((horizontalRatio << 4) | verticalRatio) @@ -930,10 +930,10 @@ namespace ImageSharp.Formats this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio); - if (this.componentCount == 4) + if (this.ComponentCount == 4) { - int h3 = this.componentArray[3].HorizontalFactor; - int v3 = this.componentArray[3].VerticalFactor; + int h3 = this.ComponentArray[3].HorizontalFactor; + int v3 = this.ComponentArray[3].VerticalFactor; this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * mxx, 8 * v3 * myy); } @@ -1017,14 +1017,14 @@ namespace ImageSharp.Formats return; } - this.ReadFull(this.temp, 0, 12); + this.ReadFull(this.Temp, 0, 12); remaining -= 12; - if (this.temp[0] == 'A' && this.temp[1] == 'd' && this.temp[2] == 'o' && this.temp[3] == 'b' - && this.temp[4] == 'e') + if (this.Temp[0] == 'A' && this.Temp[1] == 'd' && this.Temp[2] == 'o' && this.Temp[3] == 'b' + && this.Temp[4] == 'e') { this.adobeTransformValid = true; - this.adobeTransform = this.temp[11]; + this.adobeTransform = this.Temp[11]; } if (remaining > 0) @@ -1070,17 +1070,17 @@ namespace ImageSharp.Formats return; } - this.ReadFull(this.temp, 0, 13); + this.ReadFull(this.Temp, 0, 13); remaining -= 13; // TODO: We should be using constants for this. - this.isJfif = this.temp[0] == 'J' && this.temp[1] == 'F' && this.temp[2] == 'I' && this.temp[3] == 'F' - && this.temp[4] == '\x00'; + this.isJfif = this.Temp[0] == 'J' && this.Temp[1] == 'F' && this.Temp[2] == 'I' && this.Temp[3] == 'F' + && this.Temp[4] == '\x00'; if (this.isJfif) { - this.horizontalResolution = (short)(this.temp[9] + (this.temp[10] << 8)); - this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8)); + this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[10] << 8)); + this.verticalResolution = (short)(this.Temp[11] + (this.Temp[12] << 8)); } if (remaining > 0) @@ -1182,7 +1182,7 @@ namespace ImageSharp.Formats } } - if (this.isProgressive) + if (this.IsProgressive) { if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0) { @@ -1190,8 +1190,8 @@ namespace ImageSharp.Formats // TODO!!! // throw new NotImplementedException(); - // this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone(); - this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = *b; + // this.ProgCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone(); + this.ProgCoeffs[compIndex][((@by * mxx) * hi) + bx] = *b; // At this point, we could execute the rest of the loop body to dequantize and // perform the inverse DCT, to save early stages of a progressive image to the @@ -1234,13 +1234,13 @@ namespace ImageSharp.Formats totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor; - currentScan.DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); + currentScan.DcTableSelector = (byte)(this.Temp[2 + (2 * i)] >> 4); if (currentScan.DcTableSelector > HuffmanTree.MaxTh) { throw new ImageFormatException("Bad DC table selector value"); } - currentScan.AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); + currentScan.AcTableSelector = (byte)(this.Temp[2 + (2 * i)] & 0x0f); if (currentScan.AcTableSelector > HuffmanTree.MaxTh) { throw new ImageFormatException("Bad AC table selector value"); @@ -1261,22 +1261,22 @@ namespace ImageSharp.Formats throw new ImageFormatException("DHT has wrong length"); } - this.ReadFull(this.temp, 0, 17); + this.ReadFull(this.Temp, 0, 17); - int tc = this.temp[0] >> 4; + int tc = this.Temp[0] >> 4; if (tc > HuffmanTree.MaxTc) { throw new ImageFormatException("Bad Tc value"); } - int th = this.temp[0] & 0x0f; - if (th > HuffmanTree.MaxTh || (!this.isProgressive && (th > 1))) + int th = this.Temp[0] & 0x0f; + if (th > HuffmanTree.MaxTh || (!this.IsProgressive && (th > 1))) { throw new ImageFormatException("Bad Th value"); } int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th; - this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.temp, ref remaining); + this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.Temp, ref remaining); } } @@ -1292,8 +1292,8 @@ namespace ImageSharp.Formats throw new ImageFormatException("DRI has wrong length"); } - this.ReadFull(this.temp, 0, 2); - this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1]; + this.ReadFull(this.Temp, 0, 2); + this.restartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1]; } /// @@ -1327,11 +1327,11 @@ namespace ImageSharp.Formats } remaining -= Block8x8F.ScalarCount; - this.ReadFull(this.temp, 0, Block8x8F.ScalarCount); + this.ReadFull(this.Temp, 0, Block8x8F.ScalarCount); for (int i = 0; i < Block8x8F.ScalarCount; i++) { - this.quantizationTables[tq][i] = this.temp[i]; + this.QuantizationTables[tq][i] = this.Temp[i]; } break; @@ -1343,11 +1343,11 @@ namespace ImageSharp.Formats } remaining -= 2 * Block8x8F.ScalarCount; - this.ReadFull(this.temp, 0, 2 * Block8x8F.ScalarCount); + this.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount); for (int i = 0; i < Block8x8F.ScalarCount; i++) { - this.quantizationTables[tq][i] = (this.temp[2 * i] << 8) | this.temp[(2 * i) + 1]; + this.QuantizationTables[tq][i] = (this.Temp[2 * i] << 8) | this.Temp[(2 * i) + 1]; } break; @@ -1370,12 +1370,12 @@ namespace ImageSharp.Formats private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv) { // Component selector. - int cs = this.temp[1 + (2 * i)]; + int cs = this.Temp[1 + (2 * i)]; int compIndex = -1; - for (int j = 0; j < this.componentCount; j++) + for (int j = 0; j < this.ComponentCount; j++) { // Component compv = ; - if (cs == this.componentArray[j].Identifier) + if (cs == this.ComponentArray[j].Identifier) { compIndex = j; } @@ -1388,7 +1388,7 @@ namespace ImageSharp.Formats currentScan.Index = (byte)compIndex; - this.ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]); + this.ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.ComponentArray[compIndex]); } /// @@ -1397,7 +1397,7 @@ namespace ImageSharp.Formats /// The remaining bytes in the segment block. private void ProcessStartOfFrameMarker(int remaining) { - if (this.componentCount != 0) + if (this.ComponentCount != 0) { throw new ImageFormatException("Multiple SOF markers"); } @@ -1405,54 +1405,54 @@ namespace ImageSharp.Formats switch (remaining) { case 6 + (3 * 1): // Grayscale image. - this.componentCount = 1; + this.ComponentCount = 1; break; case 6 + (3 * 3): // YCbCr or RGB image. - this.componentCount = 3; + this.ComponentCount = 3; break; case 6 + (3 * 4): // YCbCrK or CMYK image. - this.componentCount = 4; + this.ComponentCount = 4; break; default: throw new ImageFormatException("Incorrect number of components"); } - this.ReadFull(this.temp, 0, remaining); + this.ReadFull(this.Temp, 0, remaining); // We only support 8-bit precision. - if (this.temp[0] != 8) + if (this.Temp[0] != 8) { throw new ImageFormatException("Only 8-Bit precision supported."); } - this.imageHeight = (this.temp[1] << 8) + this.temp[2]; - this.imageWidth = (this.temp[3] << 8) + this.temp[4]; - if (this.temp[5] != this.componentCount) + this.ImageHeight = (this.Temp[1] << 8) + this.Temp[2]; + this.ImageWidth = (this.Temp[3] << 8) + this.Temp[4]; + if (this.Temp[5] != this.ComponentCount) { throw new ImageFormatException("SOF has wrong length"); } - for (int i = 0; i < this.componentCount; i++) + for (int i = 0; i < this.ComponentCount; i++) { - this.componentArray[i].Identifier = this.temp[6 + (3 * i)]; + this.ComponentArray[i].Identifier = this.Temp[6 + (3 * i)]; // Section B.2.2 states that "the value of C_i shall be different from // the values of C_1 through C_(i-1)". for (int j = 0; j < i; j++) { - if (this.componentArray[i].Identifier == this.componentArray[j].Identifier) + if (this.ComponentArray[i].Identifier == this.ComponentArray[j].Identifier) { throw new ImageFormatException("Repeated component identifier"); } } - this.componentArray[i].Selector = this.temp[8 + (3 * i)]; - if (this.componentArray[i].Selector > MaxTq) + this.ComponentArray[i].Selector = this.Temp[8 + (3 * i)]; + if (this.ComponentArray[i].Selector > MaxTq) { throw new ImageFormatException("Bad Tq value"); } - byte hv = this.temp[7 + (3 * i)]; + byte hv = this.Temp[7 + (3 * i)]; int h = hv >> 4; int v = hv & 0x0f; if (h < 1 || h > 4 || v < 1 || v > 4) @@ -1465,7 +1465,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Lnsupported subsampling ratio"); } - switch (this.componentCount) + switch (this.ComponentCount) { case 1: @@ -1512,8 +1512,8 @@ namespace ImageSharp.Formats case 1: { // Cb. - if (this.componentArray[0].HorizontalFactor % h != 0 - || this.componentArray[0].VerticalFactor % v != 0) + if (this.ComponentArray[0].HorizontalFactor % h != 0 + || this.ComponentArray[0].VerticalFactor % v != 0) { throw new ImageFormatException("Unsupported subsampling ratio"); } @@ -1524,8 +1524,8 @@ namespace ImageSharp.Formats case 2: { // Cr. - if (this.componentArray[1].HorizontalFactor != h - || this.componentArray[1].VerticalFactor != v) + if (this.ComponentArray[1].HorizontalFactor != h + || this.ComponentArray[1].VerticalFactor != v) { throw new ImageFormatException("Unsupported subsampling ratio"); } @@ -1565,8 +1565,8 @@ namespace ImageSharp.Formats break; case 3: - if (this.componentArray[0].HorizontalFactor != h - || this.componentArray[0].VerticalFactor != v) + if (this.ComponentArray[0].HorizontalFactor != h + || this.ComponentArray[0].VerticalFactor != v) { throw new ImageFormatException("Unsupported subsampling ratio"); } @@ -1577,11 +1577,18 @@ namespace ImageSharp.Formats break; } - this.componentArray[i].HorizontalFactor = h; - this.componentArray[i].VerticalFactor = v; + this.ComponentArray[i].HorizontalFactor = h; + this.ComponentArray[i].VerticalFactor = v; } } + private void ProcessStartOfScan22(int remaining) + { + DecoderScanProcessor processor = default(DecoderScanProcessor); + DecoderScanProcessor.Init(&processor, this, remaining); + this.MakeImage(processor.mxx, processor.myy); + } + /// /// Processes the SOS (Start of scan marker). /// @@ -1595,18 +1602,18 @@ namespace ImageSharp.Formats /// private void ProcessStartOfScan(int remaining) { - if (this.componentCount == 0) + if (this.ComponentCount == 0) { throw new ImageFormatException("Missing SOF marker"); } - if (remaining < 6 || 4 + (2 * this.componentCount) < remaining || remaining % 2 != 0) + if (remaining < 6 || 4 + (2 * this.ComponentCount) < remaining || remaining % 2 != 0) { throw new ImageFormatException("SOS has wrong length"); } - this.ReadFull(this.temp, 0, remaining); - byte scanComponentCount = this.temp[0]; + this.ReadFull(this.Temp, 0, remaining); + byte scanComponentCount = this.Temp[0]; int scanComponentCountX2 = 2 * scanComponentCount; if (remaining != 4 + scanComponentCountX2) @@ -1624,7 +1631,7 @@ namespace ImageSharp.Formats // Section B.2.3 states that if there is more than one component then the // total H*V values in a scan must be <= 10. - if (this.componentCount > 1 && totalHv > 10) + if (this.ComponentCount > 1 && totalHv > 10) { throw new ImageFormatException("Total sampling factors too large."); } @@ -1648,12 +1655,12 @@ namespace ImageSharp.Formats int ah = 0; int al = 0; - if (this.isProgressive) + if (this.IsProgressive) { - zigStart = this.temp[1 + scanComponentCountX2]; - zigEnd = this.temp[2 + scanComponentCountX2]; - ah = this.temp[3 + scanComponentCountX2] >> 4; - al = this.temp[3 + scanComponentCountX2] & 0x0f; + zigStart = this.Temp[1 + scanComponentCountX2]; + zigEnd = this.Temp[2 + scanComponentCountX2]; + ah = this.Temp[3 + scanComponentCountX2] >> 4; + al = this.Temp[3 + scanComponentCountX2] & 0x0f; if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= Block8x8F.ScalarCount) { @@ -1672,24 +1679,22 @@ namespace ImageSharp.Formats } // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. - int h0 = this.componentArray[0].HorizontalFactor; - int v0 = this.componentArray[0].VerticalFactor; - int mxx = (this.imageWidth + (8 * h0) - 1) / (8 * h0); - int myy = (this.imageHeight + (8 * v0) - 1) / (8 * v0); - - this.MakeImage(mxx, myy); + int h0 = this.ComponentArray[0].HorizontalFactor; + int v0 = this.ComponentArray[0].VerticalFactor; + int mxx = (this.ImageWidth + (8 * h0) - 1) / (8 * h0); + int myy = (this.ImageHeight + (8 * v0) - 1) / (8 * v0); - if (this.isProgressive) + if (this.IsProgressive) { for (int i = 0; i < scanComponentCount; i++) { int compIndex = scan[i].Index; - if (this.progCoeffs[compIndex] == null) + if (this.ProgCoeffs[compIndex] == null) { - int size = mxx * myy * this.componentArray[compIndex].HorizontalFactor - * this.componentArray[compIndex].VerticalFactor; + int size = mxx * myy * this.ComponentArray[compIndex].HorizontalFactor + * this.ComponentArray[compIndex].VerticalFactor; - this.progCoeffs[compIndex] = new Block8x8F[size]; + this.ProgCoeffs[compIndex] = new Block8x8F[size]; } } } @@ -1716,6 +1721,10 @@ namespace ImageSharp.Formats int* unzigPtr = unzig.Data; + + this.MakeImage(mxx, myy); + + for (int my = 0; my < myy; my++) { for (int mx = 0; mx < mxx; mx++) @@ -1723,8 +1732,8 @@ namespace ImageSharp.Formats for (int i = 0; i < scanComponentCount; i++) { int compIndex = scan[i].Index; - int hi = this.componentArray[compIndex].HorizontalFactor; - int vi = this.componentArray[compIndex].VerticalFactor; + int hi = this.ComponentArray[compIndex].HorizontalFactor; + int vi = this.ComponentArray[compIndex].VerticalFactor; for (int j = 0; j < hi * vi; j++) { @@ -1761,24 +1770,24 @@ namespace ImageSharp.Formats bx = blockCount % q; by = blockCount / q; blockCount++; - if (bx * 8 >= this.imageWidth || by * 8 >= this.imageHeight) + if (bx * 8 >= this.ImageWidth || by * 8 >= this.ImageHeight) { continue; } } - int qtIndex = this.componentArray[compIndex].Selector; + int qtIndex = this.ComponentArray[compIndex].Selector; // TODO: A DecoderScanProcessor struct could clean up this mess // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. - fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex]) + fixed (Block8x8F* qtp = &this.QuantizationTables[qtIndex]) { // Load the previous partially decoded coefficients, if applicable. - if (this.isProgressive) + if (this.IsProgressive) { this.blockIndex = ((@by * mxx) * hi) + bx; - fixed (Block8x8F* bp = &this.progCoeffs[compIndex][this.blockIndex]) + fixed (Block8x8F* bp = &this.ProgCoeffs[compIndex][this.blockIndex]) { this.ProcessBlockImpl( ah, @@ -1835,8 +1844,8 @@ namespace ImageSharp.Formats { // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, // but this one assumes well-formed input, and hence the restart marker follows immediately. - this.ReadFull(this.temp, 0, 2); - if (this.temp[0] != 0xff || this.temp[1] != expectedRst) + this.ReadFull(this.Temp, 0, 2); + if (this.Temp[0] != 0xff || this.Temp[1] != expectedRst) { throw new ImageFormatException("Bad RST marker"); } @@ -2072,27 +2081,6 @@ namespace ImageSharp.Formats } } - /// - /// Represents a component scan - /// - private struct Scan - { - /// - /// Gets or sets the component index. - /// - public byte Index { get; set; } - - /// - /// Gets or sets the DC table selector - /// - public byte DcTableSelector { get; set; } - - /// - /// Gets or sets the AC table selector - /// - public byte AcTableSelector { get; set; } - } - /// /// The EOF (End of File exception). /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker From fbc870ac4bf83275d7074a97f07468b710d37138 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Tue, 27 Dec 2016 04:54:56 +0100 Subject: [PATCH 08/24] almost worx --- .../Formats/Jpg/Components/Block8x8F.cs | 7 + .../Decoder/DecoderScanProcessor.cs | 447 +++++++++++++++++- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 174 ++++--- .../Formats/Jpg/Block8x8FTests.cs | 16 + 4 files changed, 527 insertions(+), 117 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 723ccd6b8..63d8b0e54 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -373,5 +373,12 @@ namespace ImageSharp.Formats.Jpg } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Copy(Block8x8F* dest, Block8x8F* source) + { + *dest = *source; + //Unsafe.CopyBlock(dest, source, (uint)sizeof(Block8x8F)); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs index b0c44c65b..c18b9cd99 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs @@ -1,9 +1,12 @@ namespace ImageSharp.Formats.Jpg { using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; internal unsafe struct DecoderScanProcessor { + [StructLayout(LayoutKind.Sequential)] public struct ComponentData { public Block8x8F Block; @@ -12,11 +15,13 @@ public Block8x8F Temp2; + public Block8x8F QuantiazationTable; + public UnzigData Unzig; - public fixed byte ScanData [3 * JpegDecoderCore.MaxComponents]; + //public fixed byte ScanData [3 * JpegDecoderCore.MaxComponents]; - public fixed int Dc [JpegDecoderCore.MaxComponents]; + //public fixed int Dc [JpegDecoderCore.MaxComponents]; public static ComponentData Create() { @@ -24,7 +29,6 @@ data.Unzig = UnzigData.Create(); return data; } - } public struct ComponentPointers @@ -35,23 +39,31 @@ public Block8x8F* Temp2; + public Block8x8F* QuantiazationTable; + public int* Unzig; - public Scan* Scan; + //public Scan* Scan; - public int* Dc; + //public int* Dc; public ComponentPointers(ComponentData* basePtr) { this.Block = &basePtr->Block; this.Temp1 = &basePtr->Temp1; this.Temp2 = &basePtr->Temp2; + this.QuantiazationTable = &basePtr->QuantiazationTable; this.Unzig = basePtr->Unzig.Data; - this.Scan = (Scan*) basePtr->ScanData; - this.Dc = basePtr->Dc; + //this.Scan = (Scan*) basePtr->ScanData; + //this.Dc = basePtr->Dc; } } - + + //private void ResetDc() + //{ + // Unsafe.InitBlock(this.Pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); + //} + public int bx; public int by; @@ -68,17 +80,20 @@ public int myy; + public int scanComponentCount; + public ComponentData Data; public ComponentPointers Pointers; - public static void Init(DecoderScanProcessor* p, JpegDecoderCore decoder, int remaining) + public static void Init(DecoderScanProcessor* p, JpegDecoderCore decoder, int remaining, Scan[] scan) { + p->Data = ComponentData.Create(); p->Pointers = new ComponentPointers(&p->Data); - + p->InitImpl(decoder, remaining, scan); } - private void InitCommon(JpegDecoderCore decoder, int remaining) + private void InitImpl(JpegDecoderCore decoder, int remaining, Scan[] scan) { if (decoder.ComponentCount == 0) { @@ -91,9 +106,9 @@ } decoder.ReadFull(decoder.Temp, 0, remaining); - byte scanComponentCount = decoder.Temp[0]; + this.scanComponentCount = decoder.Temp[0]; - int scanComponentCountX2 = 2 * scanComponentCount; + int scanComponentCountX2 = 2 * this.scanComponentCount; if (remaining != 4 + scanComponentCountX2) { throw new ImageFormatException("SOS length inconsistent with number of components"); @@ -101,9 +116,9 @@ int totalHv = 0; - for (int i = 0; i < scanComponentCount; i++) + for (int i = 0; i < this.scanComponentCount; i++) { - this.ProcessScanImpl(decoder, i, ref this.Pointers.Scan[i], ref totalHv); + this.ProcessScanImpl(decoder, i, ref scan[i], ref totalHv, scan); } // Section B.2.3 states that if there is more than one component then the // total H*V values in a scan must be <= 10. @@ -127,7 +142,7 @@ throw new ImageFormatException("Bad spectral selection bounds"); } - if (this.zigStart != 0 && scanComponentCount != 1) + if (this.zigStart != 0 && this.scanComponentCount != 1) { throw new ImageFormatException("Progressive AC coefficients for more than one component"); } @@ -146,12 +161,12 @@ if (decoder.IsProgressive) { - for (int i = 0; i < scanComponentCount; i++) + for (int i = 0; i < this.scanComponentCount; i++) { - int compIndex = this.Pointers.Scan[i].Index; + int compIndex = scan[i].Index; if (decoder.ProgCoeffs[compIndex] == null) { - int size = mxx * myy * decoder.ComponentArray[compIndex].HorizontalFactor + int size = this.mxx * this.myy * decoder.ComponentArray[compIndex].HorizontalFactor * decoder.ComponentArray[compIndex].VerticalFactor; decoder.ProgCoeffs[compIndex] = new Block8x8F[size]; @@ -160,7 +175,7 @@ } } - private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref Scan currentScan, ref int totalHv) + private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref Scan currentScan, ref int totalHv, Scan[] scan) { // Component selector. int cs = decoder.Temp[1 + (2 * i)]; @@ -181,7 +196,7 @@ currentScan.Index = (byte)compIndex; - this.ProcessComponentImpl(decoder, i, ref currentScan, ref totalHv, ref decoder.ComponentArray[compIndex]); + this.ProcessComponentImpl(decoder, i, ref currentScan, ref totalHv, ref decoder.ComponentArray[compIndex], scan); } @@ -190,7 +205,7 @@ int i, ref Scan currentScan, ref int totalHv, - ref Component currentComponent) + ref Component currentComponent, Scan[] scan) { // Section B.2.3 states that "the value of Cs_j shall be different from // the values of Cs_1 through Cs_(j-1)". Since we have previously @@ -199,7 +214,7 @@ // into comp are unique. for (int j = 0; j < i; j++) { - if (currentScan.Index == this.Pointers.Scan[j].Index) + if (currentScan.Index == scan[j].Index) { throw new ImageFormatException("Repeated component selector"); } @@ -220,9 +235,391 @@ } } - private void InitProgressive(JpegDecoderCore decoder) + public void ProcessBlocks(JpegDecoderCore decoder, Scan[] scan, ref int[] dc) + { + int blockCount = 0; + int mcu = 0; + byte expectedRst = JpegConstants.Markers.RST0; + + for (int my = 0; my < this.myy; my++) + { + for (int mx = 0; mx < this.mxx; mx++) + { + for (int i = 0; i < this.scanComponentCount; i++) + { + int compIndex = scan[i].Index; + int hi = decoder.ComponentArray[compIndex].HorizontalFactor; + int vi = decoder.ComponentArray[compIndex].VerticalFactor; + + for (int j = 0; j < hi * vi; j++) + { + // The blocks are traversed one MCU at a time. For 4:2:0 chroma + // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. + // For a baseline 32x16 pixel image, the Y blocks visiting order is: + // 0 1 4 5 + // 2 3 6 7 + // For progressive images, the interleaved scans (those with component count > 1) + // are traversed as above, but non-interleaved scans are traversed left + // to right, top to bottom: + // 0 1 2 3 + // 4 5 6 7 + // Only DC scans (zigStart == 0) can be interleave AC scans must have + // only one component. + // To further complicate matters, for non-interleaved scans, there is no + // data for any blocks that are inside the image at the MCU level but + // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 + // progressive image consists of two 16x16 MCUs. The interleaved scans + // will process 8 Y blocks: + // 0 1 4 5 + // 2 3 6 7 + // The non-interleaved scans will process only 6 Y blocks: + // 0 1 2 + // 3 4 5 + if (this.scanComponentCount != 1) + { + this.bx = (hi * mx) + (j % hi); + this.by = (vi * my) + (j / hi); + } + else + { + int q = this.mxx * hi; + this.bx = blockCount % q; + this.by = blockCount / q; + blockCount++; + if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight) + { + continue; + } + } + + int qtIndex = decoder.ComponentArray[compIndex].Selector; + + // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. + + this.Data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + //Load the previous partially decoded coefficients, if applicable. + if (decoder.IsProgressive) + { + int blockIndex = ((this.by * this.mxx) * hi) + this.bx; + this.Data.Block = decoder.ProgCoeffs[compIndex][blockIndex]; + } + else + { + this.Data.Block.Clear(); + } + + this.ProcessBlockImpl(decoder, i, compIndex, hi, scan, ref dc); + + + //fixed (Block8x8F* qtp = &decoder.QuantizationTables[qtIndex]) + //{ + // // Load the previous partially decoded coefficients, if applicable. + // if (decoder.IsProgressive) + // { + // int blockIndex = ((@by * mxx) * hi) + bx; + + // fixed (Block8x8F* bp = &decoder.ProgCoeffs[compIndex][blockIndex]) + // { + // Unsafe.CopyBlock(this.Pointers.Block, bp, (uint)sizeof(Block8x8F)); + // } + // } + // else + // { + // this.Data.Block.Clear(); + // } + // decoder.ProcessBlockImpl(this.ah, this.Pointers.Block, this.Pointers.Temp1, this.Pointers.Temp2, this.Pointers.Unzig, scan, i, this.zigStart, this.zigEnd, this.al, dc, compIndex, this.@by, this.mxx, hi, this.bx, qtp); + //} + + + } + + // for j + } + + // for i + mcu++; + + if (decoder.RestartInterval > 0 && mcu % decoder.RestartInterval == 0 && mcu < this.mxx * this.myy) + { + // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, + // but this one assumes well-formed input, and hence the restart marker follows immediately. + decoder.ReadFull(decoder.Temp, 0, 2); + if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) + { + throw new ImageFormatException("Bad RST marker"); + } + + expectedRst++; + if (expectedRst == JpegConstants.Markers.RST7 + 1) + { + expectedRst = JpegConstants.Markers.RST0; + } + + // Reset the Huffman decoder. + decoder.Bits = default(Bits); + + // Reset the DC components, as per section F.2.1.3.1. + //this.ResetDc(); + dc = new int[JpegDecoderCore.MaxComponents]; + + // Reset the progressive decoder state, as per section G.1.2.2. + decoder.EobRun = 0; + } + } + + // for mx + } + } + + /// + /// The AC table index + /// + internal const int AcTable = 1; + + /// + /// The DC table index + /// + internal const int DcTable = 0; + + private void Refine(JpegDecoderCore decoder, ref HuffmanTree h, int delta) + { + Block8x8F* b = this.Pointers.Block; + // Refining a DC component is trivial. + if (this.zigStart == 0) + { + if (this.zigEnd != 0) + { + throw new ImageFormatException("Invalid state for zig DC component"); + } + + bool bit = decoder.DecodeBit(); + if (bit) + { + int stuff = (int)Block8x8F.GetScalarAt(b, 0); + + // int stuff = (int)b[0]; + stuff |= delta; + + // b[0] = stuff; + Block8x8F.SetScalarAt(b, 0, stuff); + } + + return; + } + + // Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3. + int zig = this.zigStart; + if (decoder.EobRun == 0) + { + for (; zig <= this.zigEnd; zig++) + { + bool done = false; + int z = 0; + byte val = decoder.DecodeHuffman(ref h); + int val0 = val >> 4; + int val1 = val & 0x0f; + + switch (val1) + { + case 0: + if (val0 != 0x0f) + { + decoder.EobRun = (ushort)(1 << val0); + if (val0 != 0) + { + decoder.EobRun |= (ushort)decoder.DecodeBits(val0); + } + + done = true; + } + + break; + case 1: + z = delta; + bool bit = decoder.DecodeBit(); + if (!bit) + { + z = -z; + } + + break; + default: + throw new ImageFormatException("Unexpected Huffman code"); + } + + if (done) + { + break; + } + + int blah = zig; + + zig = this.RefineNonZeroes(decoder, zig, val0, delta); + if (zig > this.zigEnd) + { + throw new ImageFormatException($"Too many coefficients {zig} > {this.zigEnd}"); + } + + if (z != 0) + { + // b[Unzig[zig]] = z; + Block8x8F.SetScalarAt(b, this.Pointers.Unzig[zig], z); + } + } + } + + if (decoder.EobRun > 0) + { + decoder.EobRun--; + this.RefineNonZeroes(decoder, zig,-1, delta); + } + } + + private int RefineNonZeroes(JpegDecoderCore decoder, int zig, int nz, int delta) + { + var b = this.Pointers.Block; + for (; zig <= zigEnd; zig++) + { + int u = this.Pointers.Unzig[zig]; + float bu = Block8x8F.GetScalarAt(b, u); + + // TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary? + if (bu == 0) + { + if (nz == 0) + { + break; + } + + nz--; + continue; + } + + bool bit = decoder.DecodeBit(); + if (!bit) + { + continue; + } + + if (bu >= 0) + { + // b[u] += delta; + Block8x8F.SetScalarAt(b, u, bu + delta); + } + else + { + // b[u] -= delta; + Block8x8F.SetScalarAt(b, u, bu - delta); + } + } + + return zig; + } + + private void ProcessBlockImpl(JpegDecoderCore decoder, int i, int compIndex, int hi, Scan[] scan, ref int[] dc) { - + var b = this.Pointers.Block; + //var dc = this.Pointers.Dc; + int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector; + if (this.ah != 0) + { + this.Refine(decoder, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); + } + else + { + int zig = zigStart; + if (zig == 0) + { + zig++; + + // Decode the DC coefficient, as specified in section F.2.2.1. + byte value = + decoder.DecodeHuffman( + ref decoder.HuffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); + if (value > 16) + { + throw new ImageFormatException("Excessive DC component"); + } + + int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); + dc[compIndex] += deltaDC; + + // b[0] = dc[compIndex] << al; + Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al); + } + + if (zig <= this.zigEnd && decoder.EobRun > 0) + { + decoder.EobRun--; + } + else + { + // Decode the AC coefficients, as specified in section F.2.2.2. + // Huffman huffv = ; + for (; zig <= this.zigEnd; zig++) + { + byte value = decoder.DecodeHuffman(ref decoder.HuffmanTrees[huffmannIdx]); + byte val0 = (byte)(value >> 4); + byte val1 = (byte)(value & 0x0f); + if (val1 != 0) + { + zig += val0; + if (zig > this.zigEnd) + { + break; + } + + int ac = decoder.Bits.ReceiveExtend(val1, decoder); + + // b[Unzig[zig]] = ac << al; + Block8x8F.SetScalarAt(b, this.Pointers.Unzig[zig], ac << this.al); + } + else + { + if (val0 != 0x0f) + { + decoder.EobRun = (ushort)(1 << val0); + if (val0 != 0) + { + decoder.EobRun |= (ushort)decoder.DecodeBits(val0); + } + + decoder.EobRun--; + break; + } + + zig += 0x0f; + } + } + } + } + + if (decoder.IsProgressive) + { + if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0) + { + // We haven't completely decoded this 8x8 block. Save the coefficients. + // this.ProgCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone(); + decoder.ProgCoeffs[compIndex][((this.by * this.mxx) * hi) + this.bx] = *b; + + // At this point, we could execute the rest of the loop body to dequantize and + // perform the inverse DCT, to save early stages of a progressive image to the + // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, + // the jpeg.Decode function does not return until the entire image is decoded, + // so we "continue" here to avoid wasted computation. + return; + } + } + + // Dequantize, perform the inverse DCT and store the block to the image. + Block8x8F.UnZig(b, this.Pointers.QuantiazationTable, this.Pointers.Unzig); + + DCT.TransformIDCT(ref *b, ref *this.Pointers.Temp1, ref *this.Pointers.Temp2); + + var destChannel = decoder.GetDestinationChannel(compIndex); + var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); + destArea.LoadColorsFrom(this.Pointers.Temp1, this.Pointers.Temp2); } } diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index c17a2856e..7af8e534c 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Formats using System; using System.IO; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; using System.Threading.Tasks; using ImageSharp.Formats.Jpg; @@ -19,12 +20,12 @@ namespace ImageSharp.Formats /// /// The AC table index /// - private const int AcTable = 1; + internal const int AcTable = 1; /// /// The DC table index /// - private const int DcTable = 0; + internal const int DcTable = 0; /// /// The maximum number of color components @@ -44,7 +45,7 @@ namespace ImageSharp.Formats /// /// The huffman trees /// - private readonly HuffmanTree[] huffmanTrees; + internal HuffmanTree[] HuffmanTrees { get; } /// /// Saved state between progressive-mode scans. @@ -76,11 +77,11 @@ namespace ImageSharp.Formats /// /// Holds the unprocessed bits that have been taken from the byte-stream. /// - private Bits bits; + internal Bits Bits; private JpegPixelArea blackImage; - private int blockIndex; + //private int blockIndex; /// /// The byte buffer. @@ -95,7 +96,7 @@ namespace ImageSharp.Formats /// /// End-of-Band run, specified in section G.1.2.2. /// - private ushort eobRun; + internal ushort EobRun; /// /// A grayscale image to decode to. @@ -135,7 +136,7 @@ namespace ImageSharp.Formats /// /// The restart interval /// - private int restartInterval; + internal int RestartInterval { get; private set; } /// /// The vertical resolution. Calculated if the image has a JFIF header. @@ -152,15 +153,15 @@ namespace ImageSharp.Formats /// public JpegDecoderCore() { - this.huffmanTrees = HuffmanTree.CreateHuffmanTrees(); + this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.ComponentArray = new Component[MaxComponents]; this.ProgCoeffs = new Block8x8F[MaxComponents][]; - this.bits = default(Bits); + this.Bits = default(Bits); this.bytes = Bytes.Create(); } - + /// /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent) /// It's better tho have an error code for this! @@ -333,7 +334,7 @@ namespace ImageSharp.Formats return; } - this.ProcessStartOfScan(remaining); + this.ProcessStartOfScan22(remaining); break; case JpegConstants.Markers.DRI: if (configOnly) @@ -430,9 +431,9 @@ namespace ImageSharp.Formats /// public void Dispose() { - for (int i = 0; i < this.huffmanTrees.Length; i++) + for (int i = 0; i < this.HuffmanTrees.Length; i++) { - this.huffmanTrees[i].Dispose(); + this.HuffmanTrees[i].Dispose(); } this.ycbcrImage?.Dispose(); @@ -440,7 +441,7 @@ namespace ImageSharp.Formats this.grayImage.ReturnPooled(); this.blackImage.ReturnPooled(); } - + /// /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. /// @@ -462,7 +463,7 @@ namespace ImageSharp.Formats // Unread the overshot bytes, if any. if (this.bytes.UnreadableBytes != 0) { - if (this.bits.UnreadBits >= 8) + if (this.Bits.UnreadBits >= 8) { this.UnreadByteStuffedByte(); } @@ -727,20 +728,20 @@ namespace ImageSharp.Formats /// Decodes a single bit /// /// The - private bool DecodeBit() + internal bool DecodeBit() { - if (this.bits.UnreadBits == 0) + if (this.Bits.UnreadBits == 0) { - ErrorCodes errorCode = this.bits.EnsureNBits(1, this); + ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); if (errorCode != ErrorCodes.NoError) { throw new MissingFF00Exception(); } } - bool ret = (this.bits.Accumulator & this.bits.Mask) != 0; - this.bits.UnreadBits--; - this.bits.Mask >>= 1; + bool ret = (this.Bits.Accumulator & this.Bits.Mask) != 0; + this.Bits.UnreadBits--; + this.Bits.Mask >>= 1; return ret; } @@ -749,21 +750,21 @@ namespace ImageSharp.Formats /// /// The number of bits to decode. /// The - private uint DecodeBits(int count) + internal uint DecodeBits(int count) { - if (this.bits.UnreadBits < count) + if (this.Bits.UnreadBits < count) { - ErrorCodes errorCode = this.bits.EnsureNBits(count, this); + ErrorCodes errorCode = this.Bits.EnsureNBits(count, this); if (errorCode != ErrorCodes.NoError) { throw new MissingFF00Exception(); } } - uint ret = this.bits.Accumulator >> (this.bits.UnreadBits - count); + uint ret = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); ret = (uint)(ret & ((1 << count) - 1)); - this.bits.UnreadBits -= count; - this.bits.Mask >>= count; + this.Bits.UnreadBits -= count; + this.Bits.Mask >>= count; return ret; } @@ -772,7 +773,7 @@ namespace ImageSharp.Formats /// /// The huffman value /// The - private byte DecodeHuffman(ref HuffmanTree huffmanTree) + internal byte DecodeHuffman(ref HuffmanTree huffmanTree) { // Copy stuff to the stack: if (huffmanTree.Length == 0) @@ -780,20 +781,20 @@ namespace ImageSharp.Formats throw new ImageFormatException("Uninitialized Huffman table"); } - if (this.bits.UnreadBits < 8) + if (this.Bits.UnreadBits < 8) { - ErrorCodes errorCode = this.bits.EnsureNBits(8, this); + ErrorCodes errorCode = this.Bits.EnsureNBits(8, this); if (errorCode == ErrorCodes.NoError) { ushort v = - huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - HuffmanTree.LutSize)) & 0xff]; + huffmanTree.Lut[(this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSize)) & 0xff]; if (v != 0) { byte n = (byte)((v & 0xff) - 1); - this.bits.UnreadBits -= n; - this.bits.Mask >>= n; + this.Bits.UnreadBits -= n; + this.Bits.Mask >>= n; return (byte)(v >> 8); } } @@ -806,22 +807,22 @@ namespace ImageSharp.Formats int code = 0; for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) { - if (this.bits.UnreadBits == 0) + if (this.Bits.UnreadBits == 0) { - ErrorCodes errorCode = this.bits.EnsureNBits(1, this); + ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); if (errorCode != ErrorCodes.NoError) { throw new MissingFF00Exception(); } } - if ((this.bits.Accumulator & this.bits.Mask) != 0) + if ((this.Bits.Accumulator & this.Bits.Mask) != 0) { code |= 1; } - this.bits.UnreadBits--; - this.bits.Mask >>= 1; + this.Bits.UnreadBits--; + this.Bits.Mask >>= 1; if (code <= huffmanTree.MaxCodes[i]) { @@ -834,7 +835,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Bad Huffman code"); } - private JpegPixelArea GetDestinationChannel(int compIndex) + internal JpegPixelArea GetDestinationChannel(int compIndex) { if (this.ComponentCount == 1) { @@ -1089,7 +1090,7 @@ namespace ImageSharp.Formats } } - private void ProcessBlockImpl( + internal void ProcessBlockImpl( int ah, Block8x8F* b, Block8x8F* temp1, @@ -1111,7 +1112,7 @@ namespace ImageSharp.Formats int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector; if (ah != 0) { - this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al); + this.Refine(b, ref this.HuffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al); } else { @@ -1123,22 +1124,22 @@ namespace ImageSharp.Formats // Decode the DC coefficient, as specified in section F.2.2.1. byte value = this.DecodeHuffman( - ref this.huffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); + ref this.HuffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); if (value > 16) { throw new ImageFormatException("Excessive DC component"); } - int deltaDC = this.bits.ReceiveExtend(value, this); + int deltaDC = this.Bits.ReceiveExtend(value, this); dc[compIndex] += deltaDC; // b[0] = dc[compIndex] << al; Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al); } - if (zig <= zigEnd && this.eobRun > 0) + if (zig <= zigEnd && this.EobRun > 0) { - this.eobRun--; + this.EobRun--; } else { @@ -1146,7 +1147,7 @@ namespace ImageSharp.Formats // Huffman huffv = ; for (; zig <= zigEnd; zig++) { - byte value = this.DecodeHuffman(ref this.huffmanTrees[huffmannIdx]); + byte value = this.DecodeHuffman(ref this.HuffmanTrees[huffmannIdx]); byte val0 = (byte)(value >> 4); byte val1 = (byte)(value & 0x0f); if (val1 != 0) @@ -1157,7 +1158,7 @@ namespace ImageSharp.Formats break; } - int ac = this.bits.ReceiveExtend(val1, this); + int ac = this.Bits.ReceiveExtend(val1, this); // b[Unzig[zig]] = ac << al; Block8x8F.SetScalarAt(b, unzigPtr[zig], ac << al); @@ -1166,13 +1167,13 @@ namespace ImageSharp.Formats { if (val0 != 0x0f) { - this.eobRun = (ushort)(1 << val0); + this.EobRun = (ushort)(1 << val0); if (val0 != 0) { - this.eobRun |= (ushort)this.DecodeBits(val0); + this.EobRun |= (ushort)this.DecodeBits(val0); } - this.eobRun--; + this.EobRun--; break; } @@ -1276,7 +1277,7 @@ namespace ImageSharp.Formats } int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th; - this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.Temp, ref remaining); + this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.Temp, ref remaining); } } @@ -1293,7 +1294,7 @@ namespace ImageSharp.Formats } this.ReadFull(this.Temp, 0, 2); - this.restartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1]; + this.RestartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1]; } /// @@ -1584,9 +1585,14 @@ namespace ImageSharp.Formats private void ProcessStartOfScan22(int remaining) { - DecoderScanProcessor processor = default(DecoderScanProcessor); - DecoderScanProcessor.Init(&processor, this, remaining); - this.MakeImage(processor.mxx, processor.myy); + Scan[] scan = new Scan[MaxComponents]; + int[] dc = new int[MaxComponents]; + + DecoderScanProcessor p = default(DecoderScanProcessor); + DecoderScanProcessor.Init(&p, this, remaining, scan); + this.MakeImage(p.mxx, p.myy); + p.ProcessBlocks(this, scan, ref dc); + } /// @@ -1699,7 +1705,7 @@ namespace ImageSharp.Formats } } - this.bits = default(Bits); + this.Bits = default(Bits); int mcu = 0; byte expectedRst = JpegConstants.Markers.RST0; @@ -1785,34 +1791,19 @@ namespace ImageSharp.Formats // Load the previous partially decoded coefficients, if applicable. if (this.IsProgressive) { - this.blockIndex = ((@by * mxx) * hi) + bx; - - fixed (Block8x8F* bp = &this.ProgCoeffs[compIndex][this.blockIndex]) + int blockIndex = ((@by * mxx) * hi) + bx; + + fixed (Block8x8F* bp = &this.ProgCoeffs[compIndex][blockIndex]) { - this.ProcessBlockImpl( - ah, - bp, - &temp1, - &temp2, - unzigPtr, - scan, - i, - zigStart, - zigEnd, - al, - dc, - compIndex, - @by, - mxx, - hi, - bx, - qtp); + Unsafe.CopyBlock(&b, bp, (uint) sizeof(Block8x8F) ); } } else { b.Clear(); - this.ProcessBlockImpl( + } + + this.ProcessBlockImpl( ah, &b, &temp1, @@ -1830,7 +1821,6 @@ namespace ImageSharp.Formats hi, bx, qtp); - } } } @@ -1840,7 +1830,7 @@ namespace ImageSharp.Formats // for i mcu++; - if (this.restartInterval > 0 && mcu % this.restartInterval == 0 && mcu < mxx * myy) + if (this.RestartInterval > 0 && mcu % this.RestartInterval == 0 && mcu < mxx * myy) { // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, // but this one assumes well-formed input, and hence the restart marker follows immediately. @@ -1857,13 +1847,13 @@ namespace ImageSharp.Formats } // Reset the Huffman decoder. - this.bits = default(Bits); + this.Bits = default(Bits); // Reset the DC components, as per section F.2.1.3.1. dc = new int[MaxComponents]; // Reset the progressive decoder state, as per section G.1.2.2. - this.eobRun = 0; + this.EobRun = 0; } } @@ -1909,7 +1899,7 @@ namespace ImageSharp.Formats // Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3. int zig = zigStart; - if (this.eobRun == 0) + if (this.EobRun == 0) { for (; zig <= zigEnd; zig++) { @@ -1924,10 +1914,10 @@ namespace ImageSharp.Formats case 0: if (val0 != 0x0f) { - this.eobRun = (ushort)(1 << val0); + this.EobRun = (ushort)(1 << val0); if (val0 != 0) { - this.eobRun |= (ushort)this.DecodeBits(val0); + this.EobRun |= (ushort)this.DecodeBits(val0); } done = true; @@ -1968,9 +1958,9 @@ namespace ImageSharp.Formats } } - if (this.eobRun > 0) + if (this.EobRun > 0) { - this.eobRun--; + this.EobRun--; this.RefineNonZeroes(b, zig, zigEnd, -1, delta, unzigPtr); } } @@ -2035,7 +2025,7 @@ namespace ImageSharp.Formats // Unread the overshot bytes, if any. if (this.bytes.UnreadableBytes != 0) { - if (this.bits.UnreadBits >= 8) + if (this.Bits.UnreadBits >= 8) { this.UnreadByteStuffedByte(); } @@ -2073,11 +2063,11 @@ namespace ImageSharp.Formats { this.bytes.I -= this.bytes.UnreadableBytes; this.bytes.UnreadableBytes = 0; - if (this.bits.UnreadBits >= 8) + if (this.Bits.UnreadBits >= 8) { - this.bits.Accumulator >>= 8; - this.bits.UnreadBits -= 8; - this.bits.Mask >>= 8; + this.Bits.Accumulator >>= 8; + this.Bits.UnreadBits -= 8; + this.Bits.Mask >>= 8; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 57b3c56f8..9bae8d9f3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -428,5 +428,21 @@ namespace ImageSharp.Tests Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); } + + [Fact] + public unsafe void Copy_FromHeap() + { + Block8x8F[] blox = new Block8x8F[1]; + + blox[0].LoadFrom(Create8x8FloatData()); + Block8x8F clone = default(Block8x8F); + + fixed (Block8x8F* p = &blox[0]) + { + Block8x8F.Copy(&clone, p); + } + + Assert.Equal(blox[0], clone); + } } } \ No newline at end of file From 6a276fc01843288f7f9cb5b801b8e5e68bf8c8af Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Tue, 27 Dec 2016 05:03:44 +0100 Subject: [PATCH 09/24] fixx --- .../Decoder/DecoderScanProcessor.cs | 22 ------------------- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 1 + 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs index c18b9cd99..a601d521b 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs @@ -310,28 +310,6 @@ } this.ProcessBlockImpl(decoder, i, compIndex, hi, scan, ref dc); - - - //fixed (Block8x8F* qtp = &decoder.QuantizationTables[qtIndex]) - //{ - // // Load the previous partially decoded coefficients, if applicable. - // if (decoder.IsProgressive) - // { - // int blockIndex = ((@by * mxx) * hi) + bx; - - // fixed (Block8x8F* bp = &decoder.ProgCoeffs[compIndex][blockIndex]) - // { - // Unsafe.CopyBlock(this.Pointers.Block, bp, (uint)sizeof(Block8x8F)); - // } - // } - // else - // { - // this.Data.Block.Clear(); - // } - // decoder.ProcessBlockImpl(this.ah, this.Pointers.Block, this.Pointers.Temp1, this.Pointers.Temp2, this.Pointers.Unzig, scan, i, this.zigStart, this.zigEnd, this.al, dc, compIndex, this.@by, this.mxx, hi, this.bx, qtp); - //} - - } // for j diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 7af8e534c..22d4f1ca8 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -1590,6 +1590,7 @@ namespace ImageSharp.Formats DecoderScanProcessor p = default(DecoderScanProcessor); DecoderScanProcessor.Init(&p, this, remaining, scan); + this.Bits = default(Bits); this.MakeImage(p.mxx, p.myy); p.ProcessBlocks(this, scan, ref dc); From a54e213c97f20f680abc7a1605fac7217fd36744 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Tue, 27 Dec 2016 05:39:28 +0100 Subject: [PATCH 10/24] JpegScanDecoder replaced the old stuff! --- ...derScanProcessor.cs => JpegScanDecoder.cs} | 171 +++-- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 625 +----------------- 2 files changed, 110 insertions(+), 686 deletions(-) rename src/ImageSharp/Formats/Jpg/Components/Decoder/{DecoderScanProcessor.cs => JpegScanDecoder.cs} (79%) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs similarity index 79% rename from src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs rename to src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs index a601d521b..664219b9b 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/DecoderScanProcessor.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs @@ -1,11 +1,22 @@ -namespace ImageSharp.Formats.Jpg +// ReSharper disable InconsistentNaming +namespace ImageSharp.Formats.Jpg { using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - internal unsafe struct DecoderScanProcessor + internal unsafe struct JpegScanDecoder { + /// + /// The AC table index + /// + internal const int AcTableIndex = 1; + + /// + /// The DC table index + /// + internal const int DcTableIndex = 0; + [StructLayout(LayoutKind.Sequential)] public struct ComponentData { @@ -19,9 +30,9 @@ public UnzigData Unzig; - //public fixed byte ScanData [3 * JpegDecoderCore.MaxComponents]; + public fixed byte ScanData [3 * JpegDecoderCore.MaxComponents]; - //public fixed int Dc [JpegDecoderCore.MaxComponents]; + public fixed int Dc [JpegDecoderCore.MaxComponents]; public static ComponentData Create() { @@ -43,9 +54,9 @@ public int* Unzig; - //public Scan* Scan; + public Scan* Scan; - //public int* Dc; + public int* Dc; public ComponentPointers(ComponentData* basePtr) { @@ -54,46 +65,67 @@ this.Temp2 = &basePtr->Temp2; this.QuantiazationTable = &basePtr->QuantiazationTable; this.Unzig = basePtr->Unzig.Data; - //this.Scan = (Scan*) basePtr->ScanData; - //this.Dc = basePtr->Dc; + this.Scan = (Scan*)basePtr->ScanData; + this.Dc = basePtr->Dc; } } - //private void ResetDc() - //{ - // Unsafe.InitBlock(this.Pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); - //} + private void ResetDc() + { + Unsafe.InitBlock(this.Pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); + } + + + // bx and by are the location of the current block, in units of 8x8 + // blocks: the third block in the first row has (bx, by) = (2, 0). + + private int bx; + + private int by; - public int bx; + // zigStart and zigEnd are the spectral selection bounds. + // ah and al are the successive approximation high and low values. + // The spec calls these values Ss, Se, Ah and Al. + // For progressive JPEGs, these are the two more-or-less independent + // aspects of progression. Spectral selection progression is when not + // all of a block's 64 DCT coefficients are transmitted in one pass. + // For example, three passes could transmit coefficient 0 (the DC + // component), coefficients 1-5, and coefficients 6-63, in zig-zag + // order. Successive approximation is when not all of the bits of a + // band of coefficients are transmitted in one pass. For example, + // three passes could transmit the 6 most significant bits, followed + // by the second-least significant bit, followed by the least + // significant bit. + // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. - public int by; + private int zigStart; - public int zigStart; + private int zigEnd; - public int zigEnd; + private int ah; - public int ah; + private int al; - public int al; + // XNumberOfMCUs and YNumberOfMCUs are the number of MCUs (Minimum Coded Units) in the image. - public int mxx; + public int XNumberOfMCUs; - public int myy; + public int YNumberOfMCUs; - public int scanComponentCount; + private int scanComponentCount; - public ComponentData Data; + private ComponentData Data; - public ComponentPointers Pointers; + private ComponentPointers Pointers; - public static void Init(DecoderScanProcessor* p, JpegDecoderCore decoder, int remaining, Scan[] scan) + public static void Init(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) { p->Data = ComponentData.Create(); p->Pointers = new ComponentPointers(&p->Data); - p->InitImpl(decoder, remaining, scan); + p->InitImpl(decoder, remaining); } - private void InitImpl(JpegDecoderCore decoder, int remaining, Scan[] scan) + private void InitImpl(JpegDecoderCore decoder, int remaining) { if (decoder.ComponentCount == 0) { @@ -118,7 +150,7 @@ for (int i = 0; i < this.scanComponentCount; i++) { - this.ProcessScanImpl(decoder, i, ref scan[i], ref totalHv, scan); + this.ProcessScanImpl(decoder, i, ref this.Pointers.Scan[i], ref totalHv); } // Section B.2.3 states that if there is more than one component then the // total H*V values in a scan must be <= 10. @@ -153,20 +185,20 @@ } } - // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. + // XNumberOfMCUs and YNumberOfMCUs are the number of MCUs (Minimum Coded Units) in the image. int h0 = decoder.ComponentArray[0].HorizontalFactor; int v0 = decoder.ComponentArray[0].VerticalFactor; - this.mxx = (decoder.ImageWidth + (8 * h0) - 1) / (8 * h0); - this.myy = (decoder.ImageHeight + (8 * v0) - 1) / (8 * v0); + this.XNumberOfMCUs = (decoder.ImageWidth + (8 * h0) - 1) / (8 * h0); + this.YNumberOfMCUs = (decoder.ImageHeight + (8 * v0) - 1) / (8 * v0); if (decoder.IsProgressive) { for (int i = 0; i < this.scanComponentCount; i++) { - int compIndex = scan[i].Index; + int compIndex = this.Pointers.Scan[i].Index; if (decoder.ProgCoeffs[compIndex] == null) { - int size = this.mxx * this.myy * decoder.ComponentArray[compIndex].HorizontalFactor + int size = this.XNumberOfMCUs * this.YNumberOfMCUs * decoder.ComponentArray[compIndex].HorizontalFactor * decoder.ComponentArray[compIndex].VerticalFactor; decoder.ProgCoeffs[compIndex] = new Block8x8F[size]; @@ -175,7 +207,7 @@ } } - private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref Scan currentScan, ref int totalHv, Scan[] scan) + private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref Scan currentScan, ref int totalHv) { // Component selector. int cs = decoder.Temp[1 + (2 * i)]; @@ -196,7 +228,7 @@ currentScan.Index = (byte)compIndex; - this.ProcessComponentImpl(decoder, i, ref currentScan, ref totalHv, ref decoder.ComponentArray[compIndex], scan); + this.ProcessComponentImpl(decoder, i, ref currentScan, ref totalHv, ref decoder.ComponentArray[compIndex]); } @@ -205,7 +237,7 @@ int i, ref Scan currentScan, ref int totalHv, - ref Component currentComponent, Scan[] scan) + ref Component currentComponent) { // Section B.2.3 states that "the value of Cs_j shall be different from // the values of Cs_1 through Cs_(j-1)". Since we have previously @@ -214,7 +246,7 @@ // into comp are unique. for (int j = 0; j < i; j++) { - if (currentScan.Index == scan[j].Index) + if (currentScan.Index == this.Pointers.Scan[j].Index) { throw new ImageFormatException("Repeated component selector"); } @@ -235,19 +267,19 @@ } } - public void ProcessBlocks(JpegDecoderCore decoder, Scan[] scan, ref int[] dc) + public void ProcessBlocks(JpegDecoderCore decoder) { int blockCount = 0; int mcu = 0; byte expectedRst = JpegConstants.Markers.RST0; - for (int my = 0; my < this.myy; my++) + for (int my = 0; my < this.YNumberOfMCUs; my++) { - for (int mx = 0; mx < this.mxx; mx++) + for (int mx = 0; mx < this.XNumberOfMCUs; mx++) { for (int i = 0; i < this.scanComponentCount; i++) { - int compIndex = scan[i].Index; + int compIndex = this.Pointers.Scan[i].Index; int hi = decoder.ComponentArray[compIndex].HorizontalFactor; int vi = decoder.ComponentArray[compIndex].VerticalFactor; @@ -282,7 +314,7 @@ } else { - int q = this.mxx * hi; + int q = this.XNumberOfMCUs * hi; this.bx = blockCount % q; this.by = blockCount / q; blockCount++; @@ -301,7 +333,7 @@ //Load the previous partially decoded coefficients, if applicable. if (decoder.IsProgressive) { - int blockIndex = ((this.by * this.mxx) * hi) + this.bx; + int blockIndex = ((this.by * this.XNumberOfMCUs) * hi) + this.bx; this.Data.Block = decoder.ProgCoeffs[compIndex][blockIndex]; } else @@ -309,7 +341,7 @@ this.Data.Block.Clear(); } - this.ProcessBlockImpl(decoder, i, compIndex, hi, scan, ref dc); + this.ProcessBlockImpl(decoder, i, compIndex, hi); } // for j @@ -318,7 +350,7 @@ // for i mcu++; - if (decoder.RestartInterval > 0 && mcu % decoder.RestartInterval == 0 && mcu < this.mxx * this.myy) + if (decoder.RestartInterval > 0 && mcu % decoder.RestartInterval == 0 && mcu < this.XNumberOfMCUs * this.YNumberOfMCUs) { // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, // but this one assumes well-formed input, and hence the restart marker follows immediately. @@ -338,9 +370,8 @@ decoder.Bits = default(Bits); // Reset the DC components, as per section F.2.1.3.1. - //this.ResetDc(); - dc = new int[JpegDecoderCore.MaxComponents]; - + this.ResetDc(); + // Reset the progressive decoder state, as per section G.1.2.2. decoder.EobRun = 0; } @@ -351,15 +382,11 @@ } /// - /// The AC table index + /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// - internal const int AcTable = 1; - - /// - /// The DC table index - /// - internal const int DcTable = 0; - + /// The Huffman tree + /// The low transform offset + /// The decoder instance private void Refine(JpegDecoderCore decoder, ref HuffmanTree h, int delta) { Block8x8F* b = this.Pointers.Block; @@ -430,9 +457,7 @@ { break; } - - int blah = zig; - + zig = this.RefineNonZeroes(decoder, zig, val0, delta); if (zig > this.zigEnd) { @@ -453,11 +478,20 @@ this.RefineNonZeroes(decoder, zig,-1, delta); } } - + + /// + /// Refines non-zero entries of b in zig-zag order. + /// If >= 0, the first zero entries are skipped over. + /// + /// The decoder + /// The zig-zag start index + /// The non-zero entry + /// The low transform offset + /// The private int RefineNonZeroes(JpegDecoderCore decoder, int zig, int nz, int delta) { var b = this.Pointers.Block; - for (; zig <= zigEnd; zig++) + for (; zig <= this.zigEnd; zig++) { int u = this.Pointers.Unzig[zig]; float bu = Block8x8F.GetScalarAt(b, u); @@ -495,18 +529,18 @@ return zig; } - private void ProcessBlockImpl(JpegDecoderCore decoder, int i, int compIndex, int hi, Scan[] scan, ref int[] dc) + private void ProcessBlockImpl(JpegDecoderCore decoder, int i, int compIndex, int hi) { var b = this.Pointers.Block; //var dc = this.Pointers.Dc; - int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector; + int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].AcTableSelector; if (this.ah != 0) { this.Refine(decoder, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); } else { - int zig = zigStart; + int zig = this.zigStart; if (zig == 0) { zig++; @@ -514,17 +548,17 @@ // Decode the DC coefficient, as specified in section F.2.2.1. byte value = decoder.DecodeHuffman( - ref decoder.HuffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); + ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].DcTableSelector]); if (value > 16) { throw new ImageFormatException("Excessive DC component"); } int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); - dc[compIndex] += deltaDC; + this.Pointers.Dc[compIndex] += deltaDC; // b[0] = dc[compIndex] << al; - Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al); + Block8x8F.SetScalarAt(b, 0, this.Pointers.Dc[compIndex] << al); } if (zig <= this.zigEnd && decoder.EobRun > 0) @@ -534,7 +568,6 @@ else { // Decode the AC coefficients, as specified in section F.2.2.2. - // Huffman huffv = ; for (; zig <= this.zigEnd; zig++) { byte value = decoder.DecodeHuffman(ref decoder.HuffmanTrees[huffmannIdx]); @@ -578,8 +611,8 @@ if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0) { // We haven't completely decoded this 8x8 block. Save the coefficients. - // this.ProgCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone(); - decoder.ProgCoeffs[compIndex][((this.by * this.mxx) * hi) + this.bx] = *b; + // this.ProgCoeffs[compIndex][((@by * XNumberOfMCUs) * hi) + bx] = b.Clone(); + decoder.ProgCoeffs[compIndex][((this.by * this.XNumberOfMCUs) * hi) + this.bx] = *b; // At this point, we could execute the rest of the loop body to dequantize and // perform the inverse DCT, to save early stages of a progressive image to the diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 22d4f1ca8..4b3b26da9 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -17,16 +17,6 @@ namespace ImageSharp.Formats /// internal unsafe class JpegDecoderCore : IDisposable { - /// - /// The AC table index - /// - internal const int AcTable = 1; - - /// - /// The DC table index - /// - internal const int DcTable = 0; - /// /// The maximum number of color components /// @@ -334,7 +324,7 @@ namespace ImageSharp.Formats return; } - this.ProcessStartOfScan22(remaining); + this.ProcessStartOfScan(remaining); break; case JpegConstants.Markers.DRI: if (configOnly) @@ -1090,164 +1080,6 @@ namespace ImageSharp.Formats } } - internal void ProcessBlockImpl( - int ah, - Block8x8F* b, - Block8x8F* temp1, - Block8x8F* temp2, - int* unzigPtr, - Scan[] scan, - int i, - int zigStart, - int zigEnd, - int al, - int[] dc, - int compIndex, - int @by, - int mxx, - int hi, - int bx, - Block8x8F* qt) - { - int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector; - if (ah != 0) - { - this.Refine(b, ref this.HuffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al); - } - else - { - int zig = zigStart; - if (zig == 0) - { - zig++; - - // Decode the DC coefficient, as specified in section F.2.2.1. - byte value = - this.DecodeHuffman( - ref this.HuffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]); - if (value > 16) - { - throw new ImageFormatException("Excessive DC component"); - } - - int deltaDC = this.Bits.ReceiveExtend(value, this); - dc[compIndex] += deltaDC; - - // b[0] = dc[compIndex] << al; - Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al); - } - - if (zig <= zigEnd && this.EobRun > 0) - { - this.EobRun--; - } - else - { - // Decode the AC coefficients, as specified in section F.2.2.2. - // Huffman huffv = ; - for (; zig <= zigEnd; zig++) - { - byte value = this.DecodeHuffman(ref this.HuffmanTrees[huffmannIdx]); - byte val0 = (byte)(value >> 4); - byte val1 = (byte)(value & 0x0f); - if (val1 != 0) - { - zig += val0; - if (zig > zigEnd) - { - break; - } - - int ac = this.Bits.ReceiveExtend(val1, this); - - // b[Unzig[zig]] = ac << al; - Block8x8F.SetScalarAt(b, unzigPtr[zig], ac << al); - } - else - { - if (val0 != 0x0f) - { - this.EobRun = (ushort)(1 << val0); - if (val0 != 0) - { - this.EobRun |= (ushort)this.DecodeBits(val0); - } - - this.EobRun--; - break; - } - - zig += 0x0f; - } - } - } - } - - if (this.IsProgressive) - { - if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0) - { - // We haven't completely decoded this 8x8 block. Save the coefficients. - - // TODO!!! - // throw new NotImplementedException(); - // this.ProgCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone(); - this.ProgCoeffs[compIndex][((@by * mxx) * hi) + bx] = *b; - - // At this point, we could execute the rest of the loop body to dequantize and - // perform the inverse DCT, to save early stages of a progressive image to the - // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, - // the jpeg.Decode function does not return until the entire image is decoded, - // so we "continue" here to avoid wasted computation. - return; - } - } - - // 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); - - var destChannel = this.GetDestinationChannel(compIndex); - var destArea = destChannel.GetOffsetedSubAreaForBlock(bx, by); - destArea.LoadColorsFrom(temp1, temp2); - } - - private void ProcessComponentImpl( - int i, - ref Scan currentScan, - Scan[] scan, - ref int totalHv, - ref Component currentComponent) - { - // Section B.2.3 states that "the value of Cs_j shall be different from - // the values of Cs_1 through Cs_(j-1)". Since we have previously - // verified that a frame's component identifiers (C_i values in section - // B.2.2) are unique, it suffices to check that the implicit indexes - // into comp are unique. - for (int j = 0; j < i; j++) - { - if (currentScan.Index == scan[j].Index) - { - throw new ImageFormatException("Repeated component selector"); - } - } - - totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor; - - currentScan.DcTableSelector = (byte)(this.Temp[2 + (2 * i)] >> 4); - if (currentScan.DcTableSelector > HuffmanTree.MaxTh) - { - throw new ImageFormatException("Bad DC table selector value"); - } - - currentScan.AcTableSelector = (byte)(this.Temp[2 + (2 * i)] & 0x0f); - if (currentScan.AcTableSelector > HuffmanTree.MaxTh) - { - throw new ImageFormatException("Bad AC table selector value"); - } - } - /// /// Processes a Define Huffman Table marker, and initializes a huffman /// struct from its contents. Specified in section B.2.4.2. @@ -1368,30 +1200,6 @@ namespace ImageSharp.Formats } } - private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv) - { - // Component selector. - int cs = this.Temp[1 + (2 * i)]; - int compIndex = -1; - for (int j = 0; j < this.ComponentCount; j++) - { - // Component compv = ; - if (cs == this.ComponentArray[j].Identifier) - { - compIndex = j; - } - } - - if (compIndex < 0) - { - throw new ImageFormatException("Unknown component selector"); - } - - currentScan.Index = (byte)compIndex; - - this.ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.ComponentArray[compIndex]); - } - /// /// Processes the Start of Frame marker. Specified in section B.2.2. /// @@ -1583,438 +1391,21 @@ namespace ImageSharp.Formats } } - private void ProcessStartOfScan22(int remaining) - { - Scan[] scan = new Scan[MaxComponents]; - int[] dc = new int[MaxComponents]; - - DecoderScanProcessor p = default(DecoderScanProcessor); - DecoderScanProcessor.Init(&p, this, remaining, scan); - this.Bits = default(Bits); - this.MakeImage(p.mxx, p.myy); - p.ProcessBlocks(this, scan, ref dc); - - } - /// - /// Processes the SOS (Start of scan marker). + /// Processes the SOS (Start of scan marker). /// - /// - /// TODO: This also needs some significant refactoring to follow a more OO format. - /// /// The remaining bytes in the segment block. /// - /// Missing SOF Marker - /// SOS has wrong length + /// Missing SOF Marker + /// SOS has wrong length /// private void ProcessStartOfScan(int remaining) { - if (this.ComponentCount == 0) - { - throw new ImageFormatException("Missing SOF marker"); - } - - if (remaining < 6 || 4 + (2 * this.ComponentCount) < remaining || remaining % 2 != 0) - { - throw new ImageFormatException("SOS has wrong length"); - } - - this.ReadFull(this.Temp, 0, remaining); - byte scanComponentCount = this.Temp[0]; - - int scanComponentCountX2 = 2 * scanComponentCount; - if (remaining != 4 + scanComponentCountX2) - { - throw new ImageFormatException("SOS length inconsistent with number of components"); - } - - Scan[] scan = new Scan[MaxComponents]; - int totalHv = 0; - - for (int i = 0; i < scanComponentCount; i++) - { - this.ProcessScanImpl(i, ref scan[i], scan, ref totalHv); - } - - // Section B.2.3 states that if there is more than one component then the - // total H*V values in a scan must be <= 10. - if (this.ComponentCount > 1 && totalHv > 10) - { - throw new ImageFormatException("Total sampling factors too large."); - } - - // zigStart and zigEnd are the spectral selection bounds. - // ah and al are the successive approximation high and low values. - // The spec calls these values Ss, Se, Ah and Al. - // For progressive JPEGs, these are the two more-or-less independent - // aspects of progression. Spectral selection progression is when not - // all of a block's 64 DCT coefficients are transmitted in one pass. - // For example, three passes could transmit coefficient 0 (the DC - // component), coefficients 1-5, and coefficients 6-63, in zig-zag - // order. Successive approximation is when not all of the bits of a - // band of coefficients are transmitted in one pass. For example, - // three passes could transmit the 6 most significant bits, followed - // by the second-least significant bit, followed by the least - // significant bit. - // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. - int zigStart = 0; - int zigEnd = Block8x8F.ScalarCount - 1; - int ah = 0; - int al = 0; - - if (this.IsProgressive) - { - zigStart = this.Temp[1 + scanComponentCountX2]; - zigEnd = this.Temp[2 + scanComponentCountX2]; - ah = this.Temp[3 + scanComponentCountX2] >> 4; - al = this.Temp[3 + scanComponentCountX2] & 0x0f; - - if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= Block8x8F.ScalarCount) - { - throw new ImageFormatException("Bad spectral selection bounds"); - } - - if (zigStart != 0 && scanComponentCount != 1) - { - throw new ImageFormatException("Progressive AC coefficients for more than one component"); - } - - if (ah != 0 && ah != al + 1) - { - throw new ImageFormatException("Bad successive approximation values"); - } - } - - // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. - int h0 = this.ComponentArray[0].HorizontalFactor; - int v0 = this.ComponentArray[0].VerticalFactor; - int mxx = (this.ImageWidth + (8 * h0) - 1) / (8 * h0); - int myy = (this.ImageHeight + (8 * v0) - 1) / (8 * v0); - - if (this.IsProgressive) - { - for (int i = 0; i < scanComponentCount; i++) - { - int compIndex = scan[i].Index; - if (this.ProgCoeffs[compIndex] == null) - { - int size = mxx * myy * this.ComponentArray[compIndex].HorizontalFactor - * this.ComponentArray[compIndex].VerticalFactor; - - this.ProgCoeffs[compIndex] = new Block8x8F[size]; - } - } - } - + JpegScanDecoder scan = default(JpegScanDecoder); + JpegScanDecoder.Init(&scan, this, remaining); this.Bits = default(Bits); - - int mcu = 0; - byte expectedRst = JpegConstants.Markers.RST0; - - // b is the decoded coefficients block, in natural (not zig-zag) order. - // Block b; - int[] dc = new int[MaxComponents]; - - // bx and by are the location of the current block, in units of 8x8 - // blocks: the third block in the first row has (bx, by) = (2, 0). - int bx, by, blockCount = 0; - - // TODO: A DecoderScanProcessor struct could clean up this mess - Block8x8F b = default(Block8x8F); - Block8x8F temp1 = default(Block8x8F); - Block8x8F temp2 = default(Block8x8F); - - UnzigData unzig = UnzigData.Create(); - - int* unzigPtr = unzig.Data; - - - this.MakeImage(mxx, myy); - - - for (int my = 0; my < myy; my++) - { - for (int mx = 0; mx < mxx; mx++) - { - for (int i = 0; i < scanComponentCount; i++) - { - int compIndex = scan[i].Index; - int hi = this.ComponentArray[compIndex].HorizontalFactor; - int vi = this.ComponentArray[compIndex].VerticalFactor; - - for (int j = 0; j < hi * vi; j++) - { - // The blocks are traversed one MCU at a time. For 4:2:0 chroma - // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. - // For a baseline 32x16 pixel image, the Y blocks visiting order is: - // 0 1 4 5 - // 2 3 6 7 - // For progressive images, the interleaved scans (those with component count > 1) - // are traversed as above, but non-interleaved scans are traversed left - // to right, top to bottom: - // 0 1 2 3 - // 4 5 6 7 - // Only DC scans (zigStart == 0) can be interleave AC scans must have - // only one component. - // To further complicate matters, for non-interleaved scans, there is no - // data for any blocks that are inside the image at the MCU level but - // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 - // progressive image consists of two 16x16 MCUs. The interleaved scans - // will process 8 Y blocks: - // 0 1 4 5 - // 2 3 6 7 - // The non-interleaved scans will process only 6 Y blocks: - // 0 1 2 - // 3 4 5 - if (scanComponentCount != 1) - { - bx = (hi * mx) + (j % hi); - by = (vi * my) + (j / hi); - } - else - { - int q = mxx * hi; - bx = blockCount % q; - by = blockCount / q; - blockCount++; - if (bx * 8 >= this.ImageWidth || by * 8 >= this.ImageHeight) - { - continue; - } - } - - int qtIndex = this.ComponentArray[compIndex].Selector; - - // TODO: A DecoderScanProcessor struct could clean up this mess - // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. - fixed (Block8x8F* qtp = &this.QuantizationTables[qtIndex]) - { - // Load the previous partially decoded coefficients, if applicable. - if (this.IsProgressive) - { - int blockIndex = ((@by * mxx) * hi) + bx; - - fixed (Block8x8F* bp = &this.ProgCoeffs[compIndex][blockIndex]) - { - Unsafe.CopyBlock(&b, bp, (uint) sizeof(Block8x8F) ); - } - } - else - { - b.Clear(); - } - - this.ProcessBlockImpl( - ah, - &b, - &temp1, - &temp2, - unzigPtr, - scan, - i, - zigStart, - zigEnd, - al, - dc, - compIndex, - @by, - mxx, - hi, - bx, - qtp); - } - } - - // for j - } - - // for i - mcu++; - - if (this.RestartInterval > 0 && mcu % this.RestartInterval == 0 && mcu < mxx * myy) - { - // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, - // but this one assumes well-formed input, and hence the restart marker follows immediately. - this.ReadFull(this.Temp, 0, 2); - if (this.Temp[0] != 0xff || this.Temp[1] != expectedRst) - { - throw new ImageFormatException("Bad RST marker"); - } - - expectedRst++; - if (expectedRst == JpegConstants.Markers.RST7 + 1) - { - expectedRst = JpegConstants.Markers.RST0; - } - - // Reset the Huffman decoder. - this.Bits = default(Bits); - - // Reset the DC components, as per section F.2.1.3.1. - dc = new int[MaxComponents]; - - // Reset the progressive decoder state, as per section G.1.2.2. - this.EobRun = 0; - } - } - - // for mx - } - - // for my - } - - /// - /// Decodes a successive approximation refinement block, as specified in section G.1.2. - /// - /// The block of coefficients - /// The Huffman tree - /// Unzig ptr - /// The zig-zag start index - /// The zig-zag end index - /// The low transform offset - 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) - { - if (zigEnd != 0) - { - throw new ImageFormatException("Invalid state for zig DC component"); - } - - bool bit = this.DecodeBit(); - if (bit) - { - int stuff = (int)Block8x8F.GetScalarAt(b, 0); - - // int stuff = (int)b[0]; - stuff |= delta; - - // b[0] = stuff; - Block8x8F.SetScalarAt(b, 0, stuff); - } - - return; - } - - // Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3. - int zig = zigStart; - if (this.EobRun == 0) - { - for (; zig <= zigEnd; zig++) - { - bool done = false; - int z = 0; - byte val = this.DecodeHuffman(ref h); - int val0 = val >> 4; - int val1 = val & 0x0f; - - switch (val1) - { - case 0: - if (val0 != 0x0f) - { - this.EobRun = (ushort)(1 << val0); - if (val0 != 0) - { - this.EobRun |= (ushort)this.DecodeBits(val0); - } - - done = true; - } - - break; - case 1: - z = delta; - bool bit = this.DecodeBit(); - if (!bit) - { - z = -z; - } - - break; - default: - throw new ImageFormatException("Unexpected Huffman code"); - } - - if (done) - { - break; - } - - int blah = zig; - - zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta, unzigPtr); - if (zig > zigEnd) - { - throw new ImageFormatException($"Too many coefficients {zig} > {zigEnd}"); - } - - if (z != 0) - { - // b[Unzig[zig]] = z; - Block8x8F.SetScalarAt(b, unzigPtr[zig], z); - } - } - } - - if (this.EobRun > 0) - { - this.EobRun--; - this.RefineNonZeroes(b, zig, zigEnd, -1, delta, unzigPtr); - } - } - - /// - /// Refines non-zero entries of b in zig-zag order. - /// If >= 0, the first zero entries are skipped over. - /// - /// The block of coefficients - /// The zig-zag start index - /// 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, int* unzigPtr) - { - for (; zig <= zigEnd; 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? - if (bu == 0) - { - if (nz == 0) - { - break; - } - - nz--; - continue; - } - - bool bit = this.DecodeBit(); - if (!bit) - { - continue; - } - - if (bu >= 0) - { - // b[u] += delta; - Block8x8F.SetScalarAt(b, u, bu + delta); - } - else - { - // b[u] -= delta; - Block8x8F.SetScalarAt(b, u, bu - delta); - } - } - - return zig; + this.MakeImage(scan.XNumberOfMCUs, scan.YNumberOfMCUs); + scan.ProcessBlocks(this); } /// From 34bdf4bc302fafd7c6c4be0927364a0f65c830d6 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Wed, 28 Dec 2016 19:36:50 +0100 Subject: [PATCH 11/24] JpegScanDecoder docs started --- .../Jpg/Components/Decoder/JpegScanDecoder.cs | 20 +++++++++++++-- .../Jpg/Components/Decoder/JpegScanDecoder.md | 25 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs index 664219b9b..162874f02 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs @@ -5,6 +5,10 @@ namespace ImageSharp.Formats.Jpg using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + /// + /// Encapsulates the impementation of Jpeg SOS decoder. + /// See JpegScanDecoder.md! + /// internal unsafe struct JpegScanDecoder { /// @@ -17,6 +21,9 @@ namespace ImageSharp.Formats.Jpg /// internal const int DcTableIndex = 0; + /// + /// Holds the "large" data blocks needed for computations + /// [StructLayout(LayoutKind.Sequential)] public struct ComponentData { @@ -42,6 +49,9 @@ namespace ImageSharp.Formats.Jpg } } + /// + /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of + /// public struct ComponentPointers { public Block8x8F* Block; @@ -75,12 +85,18 @@ namespace ImageSharp.Formats.Jpg Unsafe.InitBlock(this.Pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); } - - // bx and by are the location of the current block, in units of 8x8 + + // bx and by are the // blocks: the third block in the first row has (bx, by) = (2, 0). + /// + /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// private int bx; + /// + /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// private int by; // zigStart and zigEnd are the spectral selection bounds. diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md new file mode 100644 index 000000000..09e3c80bb --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md @@ -0,0 +1,25 @@ +## JpegScanDecoder +Encapsulates the impementation of the Jpeg top-to bottom scan decoder triggered by the `SOS` marker. +The implementation is optimized to hold most of the necessary data in a single value type, which is intended to be used as an on-stack object. + +#### Benefits: +- Maximized locality of reference by keeping most of the operation data on the stack +- Reaching this without long parameter lists, most of the values describing the state of the decoder algorithm +are members of the `JpegScanDecoder` struct +- Most of the logic related to Scan decoding is refactored & simplified now to live in the methods of `JpegScanDecoder` +- The first step is done towards separating the stream reading from block processing. They can be refactored later to be executed in two disctinct loops. + - The input processing loop can be `async` + - The block processing loop can be parallelized + +#### Data layout + +|JpegScanDecoder | +|-------------------| +|Variables | +|ComponentData | +|ComponentPointers | + +- **ComponentData** holds the "large" data blocks needed for computations (Mostly `Block8x8F`-s) +- **ComponentPointers** contains pointers to the memory regions of `ComponentData` so they can be easily passed around to pointer based utility methods of `Block8x8F` + + From 185578abee26360fb03c391c537fc8bd93269338 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 28 Dec 2016 19:39:09 +0100 Subject: [PATCH 12/24] IntegerReferenceDCT docs --- tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index c045d8937..e7bfdf06b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs @@ -48,6 +48,9 @@ namespace ImageSharp.Tests } } + /// + /// The "original" libjpeg/golang based DCT implementation is used as reference implementation for tests. + /// public static class IntegerReferenceDCT { private const int fix_0_298631336 = 2446; From e2da3e941ec3522034111caa80e6e509f2df7349 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 28 Dec 2016 19:45:05 +0100 Subject: [PATCH 13/24] removed Tests46 --- tests/ImageSharp.Tests46/HelloTest.cs | 29 --- .../ImageSharp.Tests46.csproj | 179 ------------------ .../Properties/AssemblyInfo.cs | 36 ---- tests/ImageSharp.Tests46/TestFile.cs | 78 -------- tests/ImageSharp.Tests46/packages.config | 10 - 5 files changed, 332 deletions(-) delete mode 100644 tests/ImageSharp.Tests46/HelloTest.cs delete mode 100644 tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj delete mode 100644 tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs delete mode 100644 tests/ImageSharp.Tests46/TestFile.cs delete mode 100644 tests/ImageSharp.Tests46/packages.config diff --git a/tests/ImageSharp.Tests46/HelloTest.cs b/tests/ImageSharp.Tests46/HelloTest.cs deleted file mode 100644 index 6a31d9bdd..000000000 --- a/tests/ImageSharp.Tests46/HelloTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index d53ee3d9c..000000000 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ /dev/null @@ -1,179 +0,0 @@ - - - - - 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\ReferenceImplementationsTests.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 deleted file mode 100644 index d16593ab0..000000000 --- a/tests/ImageSharp.Tests46/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -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 deleted file mode 100644 index 1b9842857..000000000 --- a/tests/ImageSharp.Tests46/TestFile.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// 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 string GetPath(string file) - { - return Path.Combine(FormatsDirectory, file); - } - - public static TestFile Create(string file) - { - return cache.GetOrAdd(file, (string fileName) => - { - return new TestFile(FormatsDirectory + fileName); - }); - } - - 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 deleted file mode 100644 index 4eced5344..000000000 --- a/tests/ImageSharp.Tests46/packages.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file From ce5882ab254e49c10be156899fafb636817e86d2 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Wed, 28 Dec 2016 20:23:18 +0100 Subject: [PATCH 14/24] Merge branch 'master' of https://github.com/JimBobSquarePants/ImageSharp into jpeg-optimizations-experimental Conflicts: tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs --- .../Jpg/Components/Decoder/HuffmanTree.cs | 46 +++++++++---------- .../Jpg/Components/Decoder/JpegScanDecoder.cs | 24 +++++----- .../Formats/Jpg/Components/Decoder/Scan.cs | 8 ++-- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs index bdf3468e6..44fce5cdc 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs @@ -8,77 +8,77 @@ namespace ImageSharp.Formats.Jpg using System.Buffers; /// - /// Represents a Huffman tree + /// Represents a Huffman tree /// internal struct HuffmanTree : IDisposable { /// - /// The maximum (inclusive) number of codes in a Huffman tree. + /// The maximum (inclusive) number of codes in a Huffman tree. /// public const int MaxNCodes = 256; /// - /// The maximum (inclusive) number of bits in a Huffman code. + /// The maximum (inclusive) number of bits in a Huffman code. /// public const int MaxCodeLength = 16; /// - /// The maximum number of Huffman table classes + /// The maximum number of Huffman table classes /// public const int MaxTc = 1; /// - /// The maximum number of Huffman table identifiers + /// The maximum number of Huffman table identifiers /// public const int MaxTh = 3; /// - /// Row size of the Huffman table + /// Row size of the Huffman table /// public const int ThRowSize = MaxTh + 1; /// - /// Number of Hufman Trees in the Huffman table + /// Number of Hufman Trees in the Huffman table /// public const int NumberOfTrees = (MaxTc + 1) * (MaxTh + 1); /// - /// The log-2 size of the Huffman decoder's look-up table. + /// The log-2 size of the Huffman decoder's look-up table. /// public const int LutSize = 8; /// - /// Gets or sets the number of codes in the tree. + /// Gets or sets the number of codes in the tree. /// public int Length; /// - /// Gets the look-up table for the next LutSize bits in the bit-stream. - /// The high 8 bits of the uint16 are the encoded value. The low 8 bits - /// are 1 plus the code length, or 0 if the value is too large to fit in - /// lutSize bits. + /// Gets the look-up table for the next LutSize bits in the bit-stream. + /// The high 8 bits of the uint16 are the encoded value. The low 8 bits + /// are 1 plus the code length, or 0 if the value is too large to fit in + /// lutSize bits. /// public ushort[] Lut; /// - /// Gets the the decoded values, sorted by their encoding. + /// Gets the the decoded values, sorted by their encoding. /// public byte[] Values; /// - /// Gets the array of minimum codes. - /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. + /// Gets the array of minimum codes. + /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. /// public int[] MinCodes; /// - /// Gets the array of maximum codes. - /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. + /// Gets the array of maximum codes. + /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. /// public int[] MaxCodes; /// - /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. + /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// public int[] Indices; @@ -89,7 +89,7 @@ namespace ImageSharp.Formats.Jpg private static readonly ArrayPool IntBuffer = ArrayPool.Create(MaxCodeLength, 50); /// - /// Creates and initializes an array of instances of size + /// Creates and initializes an array of instances of size /// /// An array of instances representing the Huffman tables public static HuffmanTree[] CreateHuffmanTrees() @@ -107,7 +107,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Initializes the Huffman tree + /// Initializes the Huffman tree /// private void Init() { @@ -119,7 +119,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Disposes the underlying buffers + /// Disposes the underlying buffers /// public void Dispose() { @@ -131,7 +131,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Internal part of the DHT processor, whatever does it mean + /// Internal part of the DHT processor, whatever does it mean /// /// The decoder instance /// The temporal buffer that holds the data that has been read from the Jpeg stream diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs index 162874f02..ea2ab9230 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs @@ -12,12 +12,12 @@ namespace ImageSharp.Formats.Jpg internal unsafe struct JpegScanDecoder { /// - /// The AC table index + /// The AC table index /// internal const int AcTableIndex = 1; /// - /// The DC table index + /// The DC table index /// internal const int DcTableIndex = 0; @@ -37,9 +37,9 @@ namespace ImageSharp.Formats.Jpg public UnzigData Unzig; - public fixed byte ScanData [3 * JpegDecoderCore.MaxComponents]; + public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents]; - public fixed int Dc [JpegDecoderCore.MaxComponents]; + public fixed int Dc[JpegDecoderCore.MaxComponents]; public static ComponentData Create() { @@ -133,7 +133,7 @@ namespace ImageSharp.Formats.Jpg private ComponentData Data; private ComponentPointers Pointers; - + public static void Init(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) { p->Data = ComponentData.Create(); @@ -387,7 +387,7 @@ namespace ImageSharp.Formats.Jpg // Reset the DC components, as per section F.2.1.3.1. this.ResetDc(); - + // Reset the progressive decoder state, as per section G.1.2.2. decoder.EobRun = 0; } @@ -398,7 +398,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Decodes a successive approximation refinement block, as specified in section G.1.2. + /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// /// The Huffman tree /// The low transform offset @@ -473,7 +473,7 @@ namespace ImageSharp.Formats.Jpg { break; } - + zig = this.RefineNonZeroes(decoder, zig, val0, delta); if (zig > this.zigEnd) { @@ -491,13 +491,13 @@ namespace ImageSharp.Formats.Jpg if (decoder.EobRun > 0) { decoder.EobRun--; - this.RefineNonZeroes(decoder, zig,-1, delta); + this.RefineNonZeroes(decoder, zig, -1, delta); } } /// - /// Refines non-zero entries of b in zig-zag order. - /// If >= 0, the first zero entries are skipped over. + /// Refines non-zero entries of b in zig-zag order. + /// If >= 0, the first zero entries are skipped over. /// /// The decoder /// The zig-zag start index @@ -649,5 +649,5 @@ namespace ImageSharp.Formats.Jpg destArea.LoadColorsFrom(this.Pointers.Temp1, this.Pointers.Temp2); } } - + } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs index a2f439c5a..a140d989c 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs @@ -3,23 +3,23 @@ using System.Runtime.InteropServices; /// - /// Represents a component scan + /// Represents a component scan /// [StructLayout(LayoutKind.Sequential)] internal struct Scan { /// - /// Gets or sets the component index. + /// Gets or sets the component index. /// public byte Index; /// - /// Gets or sets the DC table selector + /// Gets or sets the DC table selector /// public byte DcTableSelector; /// - /// Gets or sets the AC table selector + /// Gets or sets the AC table selector /// public byte AcTableSelector; } From d89dbec40ce3a206338526970e89556c32caf70e Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Wed, 28 Dec 2016 20:34:37 +0100 Subject: [PATCH 15/24] cleanup --- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 452 ++++++++---------- 1 file changed, 211 insertions(+), 241 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 4b3b26da9..541515cb4 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -7,139 +7,82 @@ namespace ImageSharp.Formats using System; using System.IO; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using System.Threading.Tasks; using ImageSharp.Formats.Jpg; - + /// - /// Performs the jpeg decoding operation. + /// Performs the jpeg decoding operation. /// internal unsafe class JpegDecoderCore : IDisposable { /// - /// The maximum number of color components - /// - internal const int MaxComponents = 4; - - /// - /// The maximum number of quantization tables - /// - private const int MaxTq = 3; - - /// - /// The component array - /// - internal Component[] ComponentArray { get; } - - /// - /// The huffman trees - /// - internal HuffmanTree[] HuffmanTrees { get; } - - /// - /// Saved state between progressive-mode scans. + /// The maximum number of color components /// - internal Block8x8F[][] ProgCoeffs { get; } + public const int MaxComponents = 4; /// - /// Quantization tables, in zigzag order. - /// - internal Block8x8F[] QuantizationTables { get; } - - /// - /// A temporary buffer for holding pixels - /// - internal byte[] Temp { get; } - - // TODO: the usage of this buffer is unclean + need to move it to the stack for performance - - /// - /// The App14 marker color-space + /// The App14 marker color-space /// private byte adobeTransform; /// - /// Whether the image is in CMYK format with an App14 marker + /// Whether the image is in CMYK format with an App14 marker /// private bool adobeTransformValid; /// - /// Holds the unprocessed bits that have been taken from the byte-stream. + /// Holds the unprocessed bits that have been taken from the byte-stream. /// - internal Bits Bits; - - private JpegPixelArea blackImage; - - //private int blockIndex; + public Bits Bits; /// - /// The byte buffer. + /// The byte buffer. /// - private Bytes bytes; - + public Bytes Bytes; + /// - /// The number of color components within the image. + /// End-of-Band run, specified in section G.1.2.2. /// - internal int ComponentCount { get; private set; } + public ushort EobRun; /// - /// End-of-Band run, specified in section G.1.2.2. + /// The black image to decode to. /// - internal ushort EobRun; + private JpegPixelArea blackImage; /// - /// A grayscale image to decode to. + /// A grayscale image to decode to. /// private JpegPixelArea grayImage; /// - /// The horizontal resolution. Calculated if the image has a JFIF header. + /// The horizontal resolution. Calculated if the image has a JFIF header. /// private short horizontalResolution; - - /// - /// The image height - /// - internal int ImageHeight { get; private set; } - - /// - /// The image width - /// - internal int ImageWidth { get; private set; } - + /// - /// The byte buffer. + /// The maximum number of quantization tables /// - private Stream inputStream; - + private const int MaxTq = 3; + /// - /// Whether the image has a JFIF header + /// Whether the image has a JFIF header /// private bool isJfif; - - /// - /// Whether the image is interlaced (progressive) - /// - public bool IsProgressive { get; private set; } - - /// - /// The restart interval - /// - internal int RestartInterval { get; private set; } - + /// - /// The vertical resolution. Calculated if the image has a JFIF header. + /// The vertical resolution. Calculated if the image has a JFIF header. /// private short verticalResolution; /// - /// The full color image to decode to. + /// The full color image to decode to. /// private YCbCrImage ycbcrImage; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public JpegDecoderCore() { @@ -149,56 +92,83 @@ namespace ImageSharp.Formats this.ComponentArray = new Component[MaxComponents]; this.ProgCoeffs = new Block8x8F[MaxComponents][]; this.Bits = default(Bits); - this.bytes = Bytes.Create(); + this.Bytes = Bytes.Create(); } - + /// - /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent) - /// It's better tho have an error code for this! + /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent) + /// It's better tho have an error code for this! /// internal enum ErrorCodes { /// - /// NoError + /// NoError /// NoError, /// - /// MissingFF00 + /// MissingFF00 /// MissingFF00 } + /// - /// Gets or sets the byte buffer. + /// The component array /// - public Bytes Bytes - { - get - { - return this.bytes; - } + public Component[] ComponentArray { get; } - set - { - this.bytes = value; - } - } + /// + /// The huffman trees + /// + public HuffmanTree[] HuffmanTrees { get; } /// - /// Gets the input stream. + /// Saved state between progressive-mode scans. /// - public Stream InputStream - { - get - { - return this.inputStream; - } - } + public Block8x8F[][] ProgCoeffs { get; } + + /// + /// Quantization tables, in zigzag order. + /// + public Block8x8F[] QuantizationTables { get; } + + /// + /// A temporary buffer for holding pixels + /// + // TODO: the usage rules of this buffer seem to be unclean + need to consider stack-allocating it for perf + public byte[] Temp { get; } + /// + /// The number of color components within the image. + /// + public int ComponentCount { get; private set; } + /// + /// The image height + /// + public int ImageHeight { get; private set; } + + /// + /// The image width + /// + public int ImageWidth { get; private set; } + + /// + /// Gets the input stream. + /// + public Stream InputStream { get; private set; } + /// + /// Whether the image is interlaced (progressive) + /// + public bool IsProgressive { get; private set; } /// - /// Decodes the image from the specified this._stream and sets - /// the data to image. + /// The restart interval + /// + public int RestartInterval { get; private set; } + + /// + /// Decodes the image from the specified this._stream and sets + /// the data to image. /// /// The pixel format. /// The image, where the data should be set to. @@ -207,7 +177,7 @@ namespace ImageSharp.Formats public void Decode(Image image, Stream stream, bool configOnly) where TColor : struct, IPackedPixel, IEquatable { - this.inputStream = stream; + this.InputStream = stream; // Check for the Start Of Image marker. this.ReadFull(this.Temp, 0, 2); @@ -417,7 +387,7 @@ namespace ImageSharp.Formats } /// - /// Dispose + /// Dispose /// public void Dispose() { @@ -427,23 +397,23 @@ namespace ImageSharp.Formats } this.ycbcrImage?.Dispose(); - this.bytes.Dispose(); + this.Bytes.Dispose(); this.grayImage.ReturnPooled(); this.blackImage.ReturnPooled(); } - + /// - /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. + /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. /// /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] internal byte ReadByte() { - return this.bytes.ReadByte(this.inputStream); + return this.Bytes.ReadByte(this.InputStream); } /// - /// Reads exactly length bytes into data. It does not care about byte stuffing. + /// Reads exactly length bytes into data. It does not care about byte stuffing. /// /// The data to write to. /// The offset in the source buffer @@ -451,39 +421,39 @@ namespace ImageSharp.Formats internal void ReadFull(byte[] data, int offset, int length) { // Unread the overshot bytes, if any. - if (this.bytes.UnreadableBytes != 0) + if (this.Bytes.UnreadableBytes != 0) { if (this.Bits.UnreadBits >= 8) { this.UnreadByteStuffedByte(); } - this.bytes.UnreadableBytes = 0; + this.Bytes.UnreadableBytes = 0; } while (length > 0) { - if (this.bytes.J - this.bytes.I >= length) + if (this.Bytes.J - this.Bytes.I >= length) { - Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, length); - this.bytes.I += length; + Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, length); + this.Bytes.I += length; length -= length; } else { - Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, this.bytes.J - this.bytes.I); - offset += this.bytes.J - this.bytes.I; - length -= this.bytes.J - this.bytes.I; - this.bytes.I += this.bytes.J - this.bytes.I; + Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, this.Bytes.J - this.Bytes.I); + offset += this.Bytes.J - this.Bytes.I; + length -= this.Bytes.J - this.Bytes.I; + this.Bytes.I += this.Bytes.J - this.Bytes.I; - this.bytes.Fill(this.inputStream); + this.Bytes.Fill(this.InputStream); } } } /// - /// Optimized method to pack bytes to the image from the YCbCr color space. - /// This is faster than implicit casting as it avoids double packing. + /// Optimized method to pack bytes to the image from the YCbCr color space. + /// This is faster than implicit casting as it avoids double packing. /// /// The pixel format. /// The packed pixel. @@ -505,7 +475,7 @@ namespace ImageSharp.Formats } /// - /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. + /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. /// /// The pixel format. /// The image to assign the resolution to. @@ -520,7 +490,7 @@ namespace ImageSharp.Formats } /// - /// Converts the image from the original CMYK image pixels. + /// Converts the image from the original CMYK image pixels. /// /// The pixel format. /// The image width. @@ -539,28 +509,28 @@ namespace ImageSharp.Formats 0, height, y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < width; x++) - { - byte cyan = this.ycbcrImage.YPixels[yo + x]; - byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)]; - - TColor packed = default(TColor); - this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); - pixels[x, y] = packed; - } - }); + byte cyan = this.ycbcrImage.YPixels[yo + x]; + byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)]; + + TColor packed = default(TColor); + this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); + pixels[x, y] = packed; + } + }); } this.AssignResolution(image); } /// - /// Converts the image from the original grayscale image pixels. + /// Converts the image from the original grayscale image pixels. /// /// The pixel format. /// The image width. @@ -578,24 +548,24 @@ namespace ImageSharp.Formats height, Bootstrapper.ParallelOptions, y => + { + int yoff = this.grayImage.GetRowOffset(y); + for (int x = 0; x < width; x++) { - int yoff = this.grayImage.GetRowOffset(y); - for (int x = 0; x < width; x++) - { - byte rgb = this.grayImage.Pixels[yoff + x]; - - TColor packed = default(TColor); - packed.PackFromBytes(rgb, rgb, rgb, 255); - pixels[x, y] = packed; - } - }); + byte rgb = this.grayImage.Pixels[yoff + x]; + + TColor packed = default(TColor); + packed.PackFromBytes(rgb, rgb, rgb, 255); + pixels[x, y] = packed; + } + }); } this.AssignResolution(image); } /// - /// Converts the image from the original RBG image pixels. + /// Converts the image from the original RBG image pixels. /// /// The pixel format. /// The image width. @@ -614,28 +584,28 @@ namespace ImageSharp.Formats height, Bootstrapper.ParallelOptions, y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < width; x++) - { - byte red = this.ycbcrImage.YPixels[yo + x]; - byte green = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte blue = this.ycbcrImage.CrPixels[co + (x / scale)]; - - TColor packed = default(TColor); - packed.PackFromBytes(red, green, blue, 255); - pixels[x, y] = packed; - } - }); + byte red = this.ycbcrImage.YPixels[yo + x]; + byte green = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte blue = this.ycbcrImage.CrPixels[co + (x / scale)]; + + TColor packed = default(TColor); + packed.PackFromBytes(red, green, blue, 255); + pixels[x, y] = packed; + } + }); } this.AssignResolution(image); } /// - /// Converts the image from the original YCbCr image pixels. + /// Converts the image from the original YCbCr image pixels. /// /// The pixel format. /// The image width. @@ -654,28 +624,28 @@ namespace ImageSharp.Formats height, Bootstrapper.ParallelOptions, y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < width; x++) - { - byte yy = this.ycbcrImage.YPixels[yo + x]; - byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; - - TColor packed = default(TColor); - PackYcbCr(ref packed, yy, cb, cr); - pixels[x, y] = packed; - } - }); + byte yy = this.ycbcrImage.YPixels[yo + x]; + byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; + + TColor packed = default(TColor); + PackYcbCr(ref packed, yy, cb, cr); + pixels[x, y] = packed; + } + }); } this.AssignResolution(image); } /// - /// Converts the image from the original YCCK image pixels. + /// Converts the image from the original YCCK image pixels. /// /// The pixel format. /// The image width. @@ -694,28 +664,28 @@ namespace ImageSharp.Formats 0, height, y => + { + int yo = this.ycbcrImage.GetRowYOffset(y); + int co = this.ycbcrImage.GetRowCOffset(y); + + for (int x = 0; x < width; x++) { - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < width; x++) - { - byte yy = this.ycbcrImage.YPixels[yo + x]; - byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; - - TColor packed = default(TColor); - this.PackYcck(ref packed, yy, cb, cr, x, y); - pixels[x, y] = packed; - } - }); + byte yy = this.ycbcrImage.YPixels[yo + x]; + byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; + + TColor packed = default(TColor); + this.PackYcck(ref packed, yy, cb, cr, x, y); + pixels[x, y] = packed; + } + }); } this.AssignResolution(image); } /// - /// Decodes a single bit + /// Decodes a single bit /// /// The internal bool DecodeBit() @@ -736,7 +706,7 @@ namespace ImageSharp.Formats } /// - /// Decodes the given number of bits + /// Decodes the given number of bits /// /// The number of bits to decode. /// The @@ -759,7 +729,7 @@ namespace ImageSharp.Formats } /// - /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. + /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. /// /// The huffman value /// The @@ -850,10 +820,10 @@ namespace ImageSharp.Formats } /// - /// Returns a value indicating whether the image in an RGB image. + /// Returns a value indicating whether the image in an RGB image. /// /// - /// The . + /// The . /// private bool IsRGB() { @@ -874,7 +844,7 @@ namespace ImageSharp.Formats } /// - /// Makes the image from the buffer. + /// Makes the image from the buffer. /// /// The horizontal MCU count /// The vertical MCU count @@ -932,8 +902,8 @@ namespace ImageSharp.Formats } /// - /// Optimized method to pack bytes to the image from the CMYK color space. - /// This is faster than implicit casting as it avoids double packing. + /// Optimized method to pack bytes to the image from the CMYK color space. + /// This is faster than implicit casting as it avoids double packing. /// /// The pixel format. /// The packed pixel. @@ -957,8 +927,8 @@ namespace ImageSharp.Formats } /// - /// Optimized method to pack bytes to the image from the YCCK color space. - /// This is faster than implicit casting as it avoids double packing. + /// Optimized method to pack bytes to the image from the YCCK color space. + /// This is faster than implicit casting as it avoids double packing. /// /// The pixel format. /// The packed pixel. @@ -995,9 +965,9 @@ namespace ImageSharp.Formats } /// - /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters. - /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not - /// deleted by default when deleting all metadata because it may affect the appearance of the image. + /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters. + /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not + /// deleted by default when deleting all metadata because it may affect the appearance of the image. /// /// The remaining number of bytes in the stream. private void ProcessApp14Marker(int remaining) @@ -1025,7 +995,7 @@ namespace ImageSharp.Formats } /// - /// Processes the App1 marker retrieving any stored metadata + /// Processes the App1 marker retrieving any stored metadata /// /// The pixel format. /// The remaining bytes in the segment block. @@ -1050,7 +1020,7 @@ namespace ImageSharp.Formats } /// - /// Processes the application header containing the JFIF identifier plus extra data. + /// Processes the application header containing the JFIF identifier plus extra data. /// /// The remaining bytes in the segment block. private void ProcessApplicationHeader(int remaining) @@ -1081,8 +1051,8 @@ namespace ImageSharp.Formats } /// - /// Processes a Define Huffman Table marker, and initializes a huffman - /// struct from its contents. Specified in section B.2.4.2. + /// Processes a Define Huffman Table marker, and initializes a huffman + /// struct from its contents. Specified in section B.2.4.2. /// /// The remaining bytes in the segment block. private void ProcessDefineHuffmanTablesMarker(int remaining) @@ -1114,8 +1084,8 @@ namespace ImageSharp.Formats } /// - /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in - /// macroblocks + /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in + /// macroblocks /// /// The remaining bytes in the segment block. private void ProcessDefineRestartIntervalMarker(int remaining) @@ -1130,11 +1100,11 @@ namespace ImageSharp.Formats } /// - /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. + /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. /// /// The remaining bytes in the segment block. /// - /// Thrown if the tables do not match the header + /// Thrown if the tables do not match the header /// private void ProcessDqt(int remaining) { @@ -1201,7 +1171,7 @@ namespace ImageSharp.Formats } /// - /// Processes the Start of Frame marker. Specified in section B.2.2. + /// Processes the Start of Frame marker. Specified in section B.2.2. /// /// The remaining bytes in the segment block. private void ProcessStartOfFrameMarker(int remaining) @@ -1409,52 +1379,52 @@ namespace ImageSharp.Formats } /// - /// Skips the next n bytes. + /// Skips the next n bytes. /// /// The number of bytes to ignore. private void Skip(int count) { // Unread the overshot bytes, if any. - if (this.bytes.UnreadableBytes != 0) + if (this.Bytes.UnreadableBytes != 0) { if (this.Bits.UnreadBits >= 8) { this.UnreadByteStuffedByte(); } - this.bytes.UnreadableBytes = 0; + this.Bytes.UnreadableBytes = 0; } while (true) { - int m = this.bytes.J - this.bytes.I; + int m = this.Bytes.J - this.Bytes.I; if (m > count) { m = count; } - this.bytes.I += m; + this.Bytes.I += m; count -= m; if (count == 0) { break; } - this.bytes.Fill(this.inputStream); + this.Bytes.Fill(this.InputStream); } } /// - /// Undoes the most recent ReadByteStuffedByte call, - /// giving a byte of data back from bits to bytes. The Huffman look-up table - /// requires at least 8 bits for look-up, which means that Huffman decoding can - /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot - /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte. + /// Undoes the most recent ReadByteStuffedByte call, + /// giving a byte of data back from bits to bytes. The Huffman look-up table + /// requires at least 8 bits for look-up, which means that Huffman decoding can + /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot + /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte. /// private void UnreadByteStuffedByte() { - this.bytes.I -= this.bytes.UnreadableBytes; - this.bytes.UnreadableBytes = 0; + this.Bytes.I -= this.Bytes.UnreadableBytes; + this.Bytes.UnreadableBytes = 0; if (this.Bits.UnreadBits >= 8) { this.Bits.Accumulator >>= 8; @@ -1464,22 +1434,22 @@ namespace ImageSharp.Formats } /// - /// The EOF (End of File exception). - /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker + /// The EOF (End of File exception). + /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker /// internal class EOFException : Exception { } /// - /// The missing ff00 exception. + /// The missing ff00 exception. /// internal class MissingFF00Exception : Exception { } /// - /// The short huffman data exception. + /// The short huffman data exception. /// private class ShortHuffmanDataException : Exception { From 4560f57eb5f6f4da5048c2aa5b224e6ea530a841 Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Wed, 28 Dec 2016 20:51:48 +0100 Subject: [PATCH 16/24] docs --- .../Jpg/Components/Decoder/JpegScanDecoder.cs | 292 +++++++++++------- 1 file changed, 178 insertions(+), 114 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs index ea2ab9230..34e51eaf0 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs @@ -27,20 +27,45 @@ namespace ImageSharp.Formats.Jpg [StructLayout(LayoutKind.Sequential)] public struct ComponentData { + /// + /// The main input block + /// public Block8x8F Block; + /// + /// Temporal block 1 to store intermediate and/or final computation results + /// public Block8x8F Temp1; + /// + /// Temporal block 2 to store intermediate and/or final computation results + /// public Block8x8F Temp2; + /// + /// The quantization table as + /// public Block8x8F QuantiazationTable; + /// + /// The jpeg unzig data + /// public UnzigData Unzig; + /// + /// The no-idea-what's this data + /// public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents]; + /// + /// The DC data + /// public fixed int Dc[JpegDecoderCore.MaxComponents]; + /// + /// Creates and initializes a new instance + /// + /// public static ComponentData Create() { ComponentData data = default(ComponentData); @@ -114,26 +139,56 @@ namespace ImageSharp.Formats.Jpg // significant bit. // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. + /// + /// Start index of the zig-zag selection bound + /// private int zigStart; + /// + /// End index of the zig-zag selection bound + /// private int zigEnd; + /// + /// Successive approximation high value + /// private int ah; + /// + /// Successive approximation high and low value + /// private int al; // XNumberOfMCUs and YNumberOfMCUs are the number of MCUs (Minimum Coded Units) in the image. + /// + /// Number of MCU-s (Minimum Coded Units) on X axis + /// public int XNumberOfMCUs; + /// + /// Number of MCU-s (Minimum Coded Units) in Y axis + /// public int YNumberOfMCUs; private int scanComponentCount; + /// + /// The buffer + /// private ComponentData Data; + /// + /// Pointers to elements of + /// private ComponentPointers Pointers; + /// + /// Initializes the default instance after creation + /// + /// + /// + /// public static void Init(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) { p->Data = ComponentData.Create(); @@ -141,6 +196,129 @@ namespace ImageSharp.Formats.Jpg p->InitImpl(decoder, remaining); } + /// + /// Reads the blocks from the -s stream, and processes them into the corresponding instances. + /// + /// + public void ProcessBlocks(JpegDecoderCore decoder) + { + int blockCount = 0; + int mcu = 0; + byte expectedRst = JpegConstants.Markers.RST0; + + for (int my = 0; my < this.YNumberOfMCUs; my++) + { + for (int mx = 0; mx < this.XNumberOfMCUs; mx++) + { + for (int i = 0; i < this.scanComponentCount; i++) + { + int compIndex = this.Pointers.Scan[i].Index; + int hi = decoder.ComponentArray[compIndex].HorizontalFactor; + int vi = decoder.ComponentArray[compIndex].VerticalFactor; + + for (int j = 0; j < hi * vi; j++) + { + // The blocks are traversed one MCU at a time. For 4:2:0 chroma + // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. + // For a baseline 32x16 pixel image, the Y blocks visiting order is: + // 0 1 4 5 + // 2 3 6 7 + // For progressive images, the interleaved scans (those with component count > 1) + // are traversed as above, but non-interleaved scans are traversed left + // to right, top to bottom: + // 0 1 2 3 + // 4 5 6 7 + // Only DC scans (zigStart == 0) can be interleave AC scans must have + // only one component. + // To further complicate matters, for non-interleaved scans, there is no + // data for any blocks that are inside the image at the MCU level but + // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 + // progressive image consists of two 16x16 MCUs. The interleaved scans + // will process 8 Y blocks: + // 0 1 4 5 + // 2 3 6 7 + // The non-interleaved scans will process only 6 Y blocks: + // 0 1 2 + // 3 4 5 + if (this.scanComponentCount != 1) + { + this.bx = (hi * mx) + (j % hi); + this.by = (vi * my) + (j / hi); + } + else + { + int q = this.XNumberOfMCUs * hi; + this.bx = blockCount % q; + this.by = blockCount / q; + blockCount++; + if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight) + { + continue; + } + } + + int qtIndex = decoder.ComponentArray[compIndex].Selector; + + // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. + + this.Data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + //Load the previous partially decoded coefficients, if applicable. + if (decoder.IsProgressive) + { + int blockIndex = ((this.by * this.XNumberOfMCUs) * hi) + this.bx; + this.Data.Block = decoder.ProgCoeffs[compIndex][blockIndex]; + } + else + { + this.Data.Block.Clear(); + } + + this.ProcessBlockImpl(decoder, i, compIndex, hi); + } + + // for j + } + + // for i + mcu++; + + if (decoder.RestartInterval > 0 && mcu % decoder.RestartInterval == 0 && mcu < this.XNumberOfMCUs * this.YNumberOfMCUs) + { + // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, + // but this one assumes well-formed input, and hence the restart marker follows immediately. + decoder.ReadFull(decoder.Temp, 0, 2); + if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) + { + throw new ImageFormatException("Bad RST marker"); + } + + expectedRst++; + if (expectedRst == JpegConstants.Markers.RST7 + 1) + { + expectedRst = JpegConstants.Markers.RST0; + } + + // Reset the Huffman decoder. + decoder.Bits = default(Bits); + + // Reset the DC components, as per section F.2.1.3.1. + this.ResetDc(); + + // Reset the progressive decoder state, as per section G.1.2.2. + decoder.EobRun = 0; + } + } + + // for mx + } + } + + /// + /// The implementation part of as an instance method. + /// + /// The + /// The remaining bytes private void InitImpl(JpegDecoderCore decoder, int remaining) { if (decoder.ComponentCount == 0) @@ -283,120 +461,6 @@ namespace ImageSharp.Formats.Jpg } } - public void ProcessBlocks(JpegDecoderCore decoder) - { - int blockCount = 0; - int mcu = 0; - byte expectedRst = JpegConstants.Markers.RST0; - - for (int my = 0; my < this.YNumberOfMCUs; my++) - { - for (int mx = 0; mx < this.XNumberOfMCUs; mx++) - { - for (int i = 0; i < this.scanComponentCount; i++) - { - int compIndex = this.Pointers.Scan[i].Index; - int hi = decoder.ComponentArray[compIndex].HorizontalFactor; - int vi = decoder.ComponentArray[compIndex].VerticalFactor; - - for (int j = 0; j < hi * vi; j++) - { - // The blocks are traversed one MCU at a time. For 4:2:0 chroma - // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. - // For a baseline 32x16 pixel image, the Y blocks visiting order is: - // 0 1 4 5 - // 2 3 6 7 - // For progressive images, the interleaved scans (those with component count > 1) - // are traversed as above, but non-interleaved scans are traversed left - // to right, top to bottom: - // 0 1 2 3 - // 4 5 6 7 - // Only DC scans (zigStart == 0) can be interleave AC scans must have - // only one component. - // To further complicate matters, for non-interleaved scans, there is no - // data for any blocks that are inside the image at the MCU level but - // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 - // progressive image consists of two 16x16 MCUs. The interleaved scans - // will process 8 Y blocks: - // 0 1 4 5 - // 2 3 6 7 - // The non-interleaved scans will process only 6 Y blocks: - // 0 1 2 - // 3 4 5 - if (this.scanComponentCount != 1) - { - this.bx = (hi * mx) + (j % hi); - this.by = (vi * my) + (j / hi); - } - else - { - int q = this.XNumberOfMCUs * hi; - this.bx = blockCount % q; - this.by = blockCount / q; - blockCount++; - if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight) - { - continue; - } - } - - int qtIndex = decoder.ComponentArray[compIndex].Selector; - - // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. - - this.Data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - //Load the previous partially decoded coefficients, if applicable. - if (decoder.IsProgressive) - { - int blockIndex = ((this.by * this.XNumberOfMCUs) * hi) + this.bx; - this.Data.Block = decoder.ProgCoeffs[compIndex][blockIndex]; - } - else - { - this.Data.Block.Clear(); - } - - this.ProcessBlockImpl(decoder, i, compIndex, hi); - } - - // for j - } - - // for i - mcu++; - - if (decoder.RestartInterval > 0 && mcu % decoder.RestartInterval == 0 && mcu < this.XNumberOfMCUs * this.YNumberOfMCUs) - { - // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, - // but this one assumes well-formed input, and hence the restart marker follows immediately. - decoder.ReadFull(decoder.Temp, 0, 2); - if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) - { - throw new ImageFormatException("Bad RST marker"); - } - - expectedRst++; - if (expectedRst == JpegConstants.Markers.RST7 + 1) - { - expectedRst = JpegConstants.Markers.RST0; - } - - // Reset the Huffman decoder. - decoder.Bits = default(Bits); - - // Reset the DC components, as per section F.2.1.3.1. - this.ResetDc(); - - // Reset the progressive decoder state, as per section G.1.2.2. - decoder.EobRun = 0; - } - } - - // for mx - } - } - /// /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// From da1f9f35da5a06a005166047d0fc6826950bf3ad Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 28 Dec 2016 21:11:49 +0100 Subject: [PATCH 17/24] sln changes undone --- ImageSharp.sln | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index 96dd8aa5d..afc7dce81 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -50,10 +50,6 @@ 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 @@ -62,6 +58,5 @@ 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 From 45abf75ed4864cdaa569b75c4b553b8444b6d17b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 28 Dec 2016 21:12:44 +0100 Subject: [PATCH 18/24] global.json undone --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 6da79b611..7346bdc28 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "src" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003121" } } \ No newline at end of file From 4c3bfab794268eeb31727a73f0814523a19509d7 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 29 Dec 2016 00:22:48 +0100 Subject: [PATCH 19/24] JpegTests headers + docs cleanup --- .../Jpg/Components/Decoder/JpegPixelArea.cs | 34 +++++++++---------- .../Formats/Jpg/Block8x8FTests.cs | 7 +++- .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 7 +++- .../Formats/Jpg/ReferenceImplementations.cs | 7 +++- .../Jpg/ReferenceImplementationsTests.cs | 7 +++- .../Formats/Jpg/UtilityTestClassBase.cs | 7 +++- 6 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs index 00e2b0e13..29025dc64 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs @@ -7,12 +7,12 @@ namespace ImageSharp.Formats.Jpg using System.Runtime.CompilerServices; /// - /// Represents an area of a Jpeg subimage (channel) + /// Represents an area of a Jpeg subimage (channel) /// internal struct JpegPixelArea { /// - /// Initializes a new instance of the struct from existing data. + /// Initializes a new instance of the struct from existing data. /// /// The pixel array /// The stride @@ -25,32 +25,32 @@ namespace ImageSharp.Formats.Jpg } /// - /// Gets the pixels. + /// Gets the pixels. /// public byte[] Pixels { get; private set; } /// - /// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea)) + /// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea)) /// public bool IsInitialized => this.Pixels != null; /// - /// Gets or the stride. + /// Gets or the stride. /// public int Stride { get; } /// - /// Gets or the offset. + /// Gets or the offset. /// public int Offset { get; } /// - /// Gets a of bytes to the pixel area + /// Gets a of bytes to the pixel area /// public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset); /// - /// Returns the pixel at (x, y) + /// Returns the pixel at (x, y) /// /// The x index /// The y index @@ -65,9 +65,9 @@ namespace ImageSharp.Formats.Jpg } /// - /// Creates a new instance of the struct. - /// Pixel array will be taken from a pool, this instance will be the owner of it's pixel data, therefore - /// should be called when the instance is no longer needed. + /// Creates a new instance of the struct. + /// Pixel array will be taken from a pool, this instance will be the owner of it's pixel data, therefore + /// should be called when the instance is no longer needed. /// /// The width. /// The height. @@ -80,7 +80,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Returns to the pool + /// Returns to the pool /// public void ReturnPooled() { @@ -94,7 +94,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Gets the subarea that belongs to the Block8x8 defined by block indices + /// Gets the subarea that belongs to the Block8x8 defined by block indices /// /// The block X index /// The block Y index @@ -106,7 +106,7 @@ namespace ImageSharp.Formats.Jpg } /// - /// Gets the row offset at the given position + /// Gets the row offset at the given position /// /// The y-coordinate of the image. /// The @@ -116,9 +116,9 @@ namespace ImageSharp.Formats.Jpg } /// - /// Load values to the pixel area from the given . - /// Level shift [-128.0, 128.0] floating point color values by +128, clip them to [0, 255], and convert them to - /// values + /// Load values to the pixel area from the given . + /// Level shift [-128.0, 128.0] floating point color values by +128, clip them to [0, 255], and convert them to + /// values /// /// The block holding the color values /// Temporal block provided by the caller diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 9bae8d9f3..9ac6386b1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -1,4 +1,9 @@ -// Uncomment this to turn unit tests into benchmarks: +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// Uncomment this to turn unit tests into benchmarks: //#define BENCHMARKING // ReSharper disable InconsistentNaming diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 46847873a..81156d970 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -1,4 +1,9 @@ -using System; +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index d3f29eb25..6aed0d3fa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs @@ -1,4 +1,9 @@ -// ReSharper disable InconsistentNaming +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming namespace ImageSharp.Tests { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index dcccc58be..66ddeabc0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -1,4 +1,9 @@ -// ReSharper disable InconsistentNaming +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming namespace ImageSharp.Tests.Formats.Jpg { using System.Numerics; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs index 650b9fc07..c92c6aa9a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs @@ -1,4 +1,9 @@ -using System.Text; +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System.Text; using ImageSharp.Formats; using Xunit.Abstractions; // ReSharper disable InconsistentNaming From 4bfb4d4d11f002516c1e83b3010a12fc98daa015 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 29 Dec 2016 00:47:11 +0100 Subject: [PATCH 20/24] JpegScanDecoder docs --- .../Jpg/Components/Decoder/JpegScanDecoder.cs | 293 ++++++++++-------- .../Jpg/Components/Decoder/JpegScanDecoder.md | 8 +- 2 files changed, 166 insertions(+), 135 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs index 34e51eaf0..d12f00ada 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Formats.Jpg /// Holds the "large" data blocks needed for computations /// [StructLayout(LayoutKind.Sequential)] - public struct ComponentData + public struct ComputationData { /// /// The main input block @@ -58,42 +58,63 @@ namespace ImageSharp.Formats.Jpg public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents]; /// - /// The DC data + /// The DC component values /// public fixed int Dc[JpegDecoderCore.MaxComponents]; /// - /// Creates and initializes a new instance + /// Creates and initializes a new instance /// /// - public static ComponentData Create() + public static ComputationData Create() { - ComponentData data = default(ComponentData); + ComputationData data = default(ComputationData); data.Unzig = UnzigData.Create(); return data; } } /// - /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of + /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of /// - public struct ComponentPointers + public struct DataPointers { + /// + /// Pointer to + /// public Block8x8F* Block; + /// + /// Pointer to + /// public Block8x8F* Temp1; + /// + /// Pointer to + /// public Block8x8F* Temp2; + /// + /// Pointer to + /// public Block8x8F* QuantiazationTable; + /// + /// Pointer to as int* + /// public int* Unzig; + /// + /// Pointer to as Scan* + /// public Scan* Scan; + /// + /// Pointer to + /// public int* Dc; - public ComponentPointers(ComponentData* basePtr) + public DataPointers(ComputationData* basePtr) { this.Block = &basePtr->Block; this.Temp1 = &basePtr->Temp1; @@ -171,35 +192,38 @@ namespace ImageSharp.Formats.Jpg /// public int YNumberOfMCUs; - private int scanComponentCount; + /// + /// The number of component scans + /// + private int componentScanCount; /// - /// The buffer + /// The buffer /// - private ComponentData Data; + private ComputationData Data; /// /// Pointers to elements of /// - private ComponentPointers Pointers; + private DataPointers Pointers; /// - /// Initializes the default instance after creation + /// Initializes the default instance after creation. /// - /// - /// - /// + /// Pointer to on the stack + /// The instance + /// The remaining bytes in the segment block. public static void Init(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) { - p->Data = ComponentData.Create(); - p->Pointers = new ComponentPointers(&p->Data); + p->Data = ComputationData.Create(); + p->Pointers = new DataPointers(&p->Data); p->InitImpl(decoder, remaining); } /// /// Reads the blocks from the -s stream, and processes them into the corresponding instances. /// - /// + /// The instance public void ProcessBlocks(JpegDecoderCore decoder) { int blockCount = 0; @@ -210,7 +234,7 @@ namespace ImageSharp.Formats.Jpg { for (int mx = 0; mx < this.XNumberOfMCUs; mx++) { - for (int i = 0; i < this.scanComponentCount; i++) + for (int i = 0; i < this.componentScanCount; i++) { int compIndex = this.Pointers.Scan[i].Index; int hi = decoder.ComponentArray[compIndex].HorizontalFactor; @@ -240,7 +264,7 @@ namespace ImageSharp.Formats.Jpg // The non-interleaved scans will process only 6 Y blocks: // 0 1 2 // 3 4 5 - if (this.scanComponentCount != 1) + if (this.componentScanCount != 1) { this.bx = (hi * mx) + (j % hi); this.by = (vi * my) + (j / hi); @@ -332,9 +356,9 @@ namespace ImageSharp.Formats.Jpg } decoder.ReadFull(decoder.Temp, 0, remaining); - this.scanComponentCount = decoder.Temp[0]; + this.componentScanCount = decoder.Temp[0]; - int scanComponentCountX2 = 2 * this.scanComponentCount; + int scanComponentCountX2 = 2 * this.componentScanCount; if (remaining != 4 + scanComponentCountX2) { throw new ImageFormatException("SOS length inconsistent with number of components"); @@ -342,10 +366,11 @@ namespace ImageSharp.Formats.Jpg int totalHv = 0; - for (int i = 0; i < this.scanComponentCount; i++) + for (int i = 0; i < this.componentScanCount; i++) { this.ProcessScanImpl(decoder, i, ref this.Pointers.Scan[i], ref totalHv); } + // Section B.2.3 states that if there is more than one component then the // total H*V values in a scan must be <= 10. if (decoder.ComponentCount > 1 && totalHv > 10) @@ -368,7 +393,7 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("Bad spectral selection bounds"); } - if (this.zigStart != 0 && this.scanComponentCount != 1) + if (this.zigStart != 0 && this.componentScanCount != 1) { throw new ImageFormatException("Progressive AC coefficients for more than one component"); } @@ -387,7 +412,7 @@ namespace ImageSharp.Formats.Jpg if (decoder.IsProgressive) { - for (int i = 0; i < this.scanComponentCount; i++) + for (int i = 0; i < this.componentScanCount; i++) { int compIndex = this.Pointers.Scan[i].Index; if (decoder.ProgCoeffs[compIndex] == null) @@ -401,6 +426,117 @@ namespace ImageSharp.Formats.Jpg } } + /// + /// Process the current block at (, ) + /// + /// The decoder + /// The index of the scan + /// The component index + /// Horizontal sampling factor at the given component index + private void ProcessBlockImpl(JpegDecoderCore decoder, int i, int compIndex, int hi) + { + var b = this.Pointers.Block; + //var dc = this.Pointers.Dc; + int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].AcTableSelector; + if (this.ah != 0) + { + this.Refine(decoder, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); + } + else + { + int zig = this.zigStart; + if (zig == 0) + { + zig++; + + // Decode the DC coefficient, as specified in section F.2.2.1. + byte value = + decoder.DecodeHuffman( + ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].DcTableSelector]); + if (value > 16) + { + throw new ImageFormatException("Excessive DC component"); + } + + int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); + this.Pointers.Dc[compIndex] += deltaDC; + + // b[0] = dc[compIndex] << al; + Block8x8F.SetScalarAt(b, 0, this.Pointers.Dc[compIndex] << al); + } + + if (zig <= this.zigEnd && decoder.EobRun > 0) + { + decoder.EobRun--; + } + else + { + // Decode the AC coefficients, as specified in section F.2.2.2. + for (; zig <= this.zigEnd; zig++) + { + byte value = decoder.DecodeHuffman(ref decoder.HuffmanTrees[huffmannIdx]); + byte val0 = (byte)(value >> 4); + byte val1 = (byte)(value & 0x0f); + if (val1 != 0) + { + zig += val0; + if (zig > this.zigEnd) + { + break; + } + + int ac = decoder.Bits.ReceiveExtend(val1, decoder); + + // b[Unzig[zig]] = ac << al; + Block8x8F.SetScalarAt(b, this.Pointers.Unzig[zig], ac << this.al); + } + else + { + if (val0 != 0x0f) + { + decoder.EobRun = (ushort)(1 << val0); + if (val0 != 0) + { + decoder.EobRun |= (ushort)decoder.DecodeBits(val0); + } + + decoder.EobRun--; + break; + } + + zig += 0x0f; + } + } + } + } + + if (decoder.IsProgressive) + { + if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0) + { + // We haven't completely decoded this 8x8 block. Save the coefficients. + // this.ProgCoeffs[compIndex][((@by * XNumberOfMCUs) * hi) + bx] = b.Clone(); + decoder.ProgCoeffs[compIndex][((this.by * this.XNumberOfMCUs) * hi) + this.bx] = *b; + + // At this point, we could execute the rest of the loop body to dequantize and + // perform the inverse DCT, to save early stages of a progressive image to the + // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, + // the jpeg.Decode function does not return until the entire image is decoded, + // so we "continue" here to avoid wasted computation. + return; + } + } + + // Dequantize, perform the inverse DCT and store the block to the image. + Block8x8F.UnZig(b, this.Pointers.QuantiazationTable, this.Pointers.Unzig); + + DCT.TransformIDCT(ref *b, ref *this.Pointers.Temp1, ref *this.Pointers.Temp2); + + var destChannel = decoder.GetDestinationChannel(compIndex); + var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); + destArea.LoadColorsFrom(this.Pointers.Temp1, this.Pointers.Temp2); + } + private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref Scan currentScan, ref int totalHv) { // Component selector. @@ -608,110 +744,5 @@ namespace ImageSharp.Formats.Jpg return zig; } - - private void ProcessBlockImpl(JpegDecoderCore decoder, int i, int compIndex, int hi) - { - var b = this.Pointers.Block; - //var dc = this.Pointers.Dc; - int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].AcTableSelector; - if (this.ah != 0) - { - this.Refine(decoder, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); - } - else - { - int zig = this.zigStart; - if (zig == 0) - { - zig++; - - // Decode the DC coefficient, as specified in section F.2.2.1. - byte value = - decoder.DecodeHuffman( - ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].DcTableSelector]); - if (value > 16) - { - throw new ImageFormatException("Excessive DC component"); - } - - int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); - this.Pointers.Dc[compIndex] += deltaDC; - - // b[0] = dc[compIndex] << al; - Block8x8F.SetScalarAt(b, 0, this.Pointers.Dc[compIndex] << al); - } - - if (zig <= this.zigEnd && decoder.EobRun > 0) - { - decoder.EobRun--; - } - else - { - // Decode the AC coefficients, as specified in section F.2.2.2. - for (; zig <= this.zigEnd; zig++) - { - byte value = decoder.DecodeHuffman(ref decoder.HuffmanTrees[huffmannIdx]); - byte val0 = (byte)(value >> 4); - byte val1 = (byte)(value & 0x0f); - if (val1 != 0) - { - zig += val0; - if (zig > this.zigEnd) - { - break; - } - - int ac = decoder.Bits.ReceiveExtend(val1, decoder); - - // b[Unzig[zig]] = ac << al; - Block8x8F.SetScalarAt(b, this.Pointers.Unzig[zig], ac << this.al); - } - else - { - if (val0 != 0x0f) - { - decoder.EobRun = (ushort)(1 << val0); - if (val0 != 0) - { - decoder.EobRun |= (ushort)decoder.DecodeBits(val0); - } - - decoder.EobRun--; - break; - } - - zig += 0x0f; - } - } - } - } - - if (decoder.IsProgressive) - { - if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0) - { - // We haven't completely decoded this 8x8 block. Save the coefficients. - // this.ProgCoeffs[compIndex][((@by * XNumberOfMCUs) * hi) + bx] = b.Clone(); - decoder.ProgCoeffs[compIndex][((this.by * this.XNumberOfMCUs) * hi) + this.bx] = *b; - - // At this point, we could execute the rest of the loop body to dequantize and - // perform the inverse DCT, to save early stages of a progressive image to the - // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, - // the jpeg.Decode function does not return until the entire image is decoded, - // so we "continue" here to avoid wasted computation. - return; - } - } - - // Dequantize, perform the inverse DCT and store the block to the image. - Block8x8F.UnZig(b, this.Pointers.QuantiazationTable, this.Pointers.Unzig); - - DCT.TransformIDCT(ref *b, ref *this.Pointers.Temp1, ref *this.Pointers.Temp2); - - var destChannel = decoder.GetDestinationChannel(compIndex); - var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); - destArea.LoadColorsFrom(this.Pointers.Temp1, this.Pointers.Temp2); - } } - } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md index 09e3c80bb..215f21807 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.md @@ -16,10 +16,10 @@ are members of the `JpegScanDecoder` struct |JpegScanDecoder | |-------------------| |Variables | -|ComponentData | -|ComponentPointers | +|ComputationData | +|DataPointers | -- **ComponentData** holds the "large" data blocks needed for computations (Mostly `Block8x8F`-s) -- **ComponentPointers** contains pointers to the memory regions of `ComponentData` so they can be easily passed around to pointer based utility methods of `Block8x8F` +- **ComputationData** holds the "large" data blocks needed for computations (Mostly `Block8x8F`-s) +- **DataPointers** contains pointers to the memory regions of `ComponentData` so they can be easily passed around to pointer based utility methods of `Block8x8F` From 260349bfb1b62d617c6a675cb142a76daef4867e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 29 Dec 2016 01:32:27 +0100 Subject: [PATCH 21/24] ok StyleCop, this time I won! --- .../Formats/Jpg/Components/Block8x8F.cs | 7 - .../Jpg/Components/Decoder/HuffmanTree.cs | 26 +- .../Jpg/Components/Decoder/JpegScanDecoder.cs | 341 +++++++++--------- .../Formats/Jpg/Components/Decoder/Scan.cs | 7 +- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 339 ++++++++--------- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 6 +- .../Formats/Jpg/Block8x8FTests.cs | 16 - 7 files changed, 368 insertions(+), 374 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 63d8b0e54..723ccd6b8 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -373,12 +373,5 @@ namespace ImageSharp.Formats.Jpg } } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(Block8x8F* dest, Block8x8F* source) - { - *dest = *source; - //Unsafe.CopyBlock(dest, source, (uint)sizeof(Block8x8F)); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs index 44fce5cdc..e06d644a7 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs @@ -106,18 +106,6 @@ namespace ImageSharp.Formats.Jpg return result; } - /// - /// Initializes the Huffman tree - /// - private void Init() - { - this.Lut = UshortBuffer.Rent(1 << LutSize); - this.Values = ByteBuffer.Rent(MaxNCodes); - this.MinCodes = IntBuffer.Rent(MaxCodeLength); - this.MaxCodes = IntBuffer.Rent(MaxCodeLength); - this.Indices = IntBuffer.Rent(MaxCodeLength); - } - /// /// Disposes the underlying buffers /// @@ -136,7 +124,7 @@ namespace ImageSharp.Formats.Jpg /// The decoder instance /// The temporal buffer that holds the data that has been read from the Jpeg stream /// Remaining bits - internal void ProcessDefineHuffmanTablesMarkerLoop( + public void ProcessDefineHuffmanTablesMarkerLoop( JpegDecoderCore decoder, byte[] defineHuffmanTablesData, ref int remaining) @@ -226,5 +214,17 @@ namespace ImageSharp.Formats.Jpg c <<= 1; } } + + /// + /// Initializes the Huffman tree + /// + private void Init() + { + this.Lut = UshortBuffer.Rent(1 << LutSize); + this.Values = ByteBuffer.Rent(MaxNCodes); + this.MinCodes = IntBuffer.Rent(MaxCodeLength); + this.MaxCodes = IntBuffer.Rent(MaxCodeLength); + this.Indices = IntBuffer.Rent(MaxCodeLength); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs index d12f00ada..39ee6687b 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegScanDecoder.cs @@ -1,4 +1,9 @@ -// ReSharper disable InconsistentNaming +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming namespace ImageSharp.Formats.Jpg { using System; @@ -12,128 +17,24 @@ namespace ImageSharp.Formats.Jpg internal unsafe struct JpegScanDecoder { /// - /// The AC table index + /// Number of MCU-s (Minimum Coded Units) in the image along the X axis /// - internal const int AcTableIndex = 1; + public int XNumberOfMCUs; /// - /// The DC table index + /// Number of MCU-s (Minimum Coded Units) in the image along the Y axis /// - internal const int DcTableIndex = 0; + public int YNumberOfMCUs; /// - /// Holds the "large" data blocks needed for computations + /// The AC table index /// - [StructLayout(LayoutKind.Sequential)] - public struct ComputationData - { - /// - /// The main input block - /// - public Block8x8F Block; - - /// - /// Temporal block 1 to store intermediate and/or final computation results - /// - public Block8x8F Temp1; - - /// - /// Temporal block 2 to store intermediate and/or final computation results - /// - public Block8x8F Temp2; - - /// - /// The quantization table as - /// - public Block8x8F QuantiazationTable; - - /// - /// The jpeg unzig data - /// - public UnzigData Unzig; - - /// - /// The no-idea-what's this data - /// - public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents]; - - /// - /// The DC component values - /// - public fixed int Dc[JpegDecoderCore.MaxComponents]; - - /// - /// Creates and initializes a new instance - /// - /// - public static ComputationData Create() - { - ComputationData data = default(ComputationData); - data.Unzig = UnzigData.Create(); - return data; - } - } + private const int AcTableIndex = 1; /// - /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of + /// The DC table index /// - public struct DataPointers - { - /// - /// Pointer to - /// - public Block8x8F* Block; - - /// - /// Pointer to - /// - public Block8x8F* Temp1; - - /// - /// Pointer to - /// - public Block8x8F* Temp2; - - /// - /// Pointer to - /// - public Block8x8F* QuantiazationTable; - - /// - /// Pointer to as int* - /// - public int* Unzig; - - /// - /// Pointer to as Scan* - /// - public Scan* Scan; - - /// - /// Pointer to - /// - public int* Dc; - - public DataPointers(ComputationData* basePtr) - { - this.Block = &basePtr->Block; - this.Temp1 = &basePtr->Temp1; - this.Temp2 = &basePtr->Temp2; - this.QuantiazationTable = &basePtr->QuantiazationTable; - this.Unzig = basePtr->Unzig.Data; - this.Scan = (Scan*)basePtr->ScanData; - this.Dc = basePtr->Dc; - } - } - - private void ResetDc() - { - Unsafe.InitBlock(this.Pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); - } - - - // bx and by are the - // blocks: the third block in the first row has (bx, by) = (2, 0). + private const int DcTableIndex = 0; /// /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) @@ -180,32 +81,25 @@ namespace ImageSharp.Formats.Jpg /// private int al; - // XNumberOfMCUs and YNumberOfMCUs are the number of MCUs (Minimum Coded Units) in the image. - /// - /// Number of MCU-s (Minimum Coded Units) on X axis - /// - public int XNumberOfMCUs; - - /// - /// Number of MCU-s (Minimum Coded Units) in Y axis + /// The number of component scans /// - public int YNumberOfMCUs; + private int componentScanCount; /// - /// The number of component scans + /// End-of-Band run, specified in section G.1.2.2. /// - private int componentScanCount; + private ushort eobRun; /// /// The buffer /// - private ComputationData Data; + private ComputationData data; /// - /// Pointers to elements of + /// Pointers to elements of /// - private DataPointers Pointers; + private DataPointers pointers; /// /// Initializes the default instance after creation. @@ -215,8 +109,8 @@ namespace ImageSharp.Formats.Jpg /// The remaining bytes in the segment block. public static void Init(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) { - p->Data = ComputationData.Create(); - p->Pointers = new DataPointers(&p->Data); + p->data = ComputationData.Create(); + p->pointers = new DataPointers(&p->data); p->InitImpl(decoder, remaining); } @@ -236,7 +130,7 @@ namespace ImageSharp.Formats.Jpg { for (int i = 0; i < this.componentScanCount; i++) { - int compIndex = this.Pointers.Scan[i].Index; + int compIndex = this.pointers.Scan[i].Index; int hi = decoder.ComponentArray[compIndex].HorizontalFactor; int vi = decoder.ComponentArray[compIndex].VerticalFactor; @@ -284,18 +178,17 @@ namespace ImageSharp.Formats.Jpg int qtIndex = decoder.ComponentArray[compIndex].Selector; // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - this.Data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - //Load the previous partially decoded coefficients, if applicable. + // Load the previous partially decoded coefficients, if applicable. if (decoder.IsProgressive) { int blockIndex = ((this.by * this.XNumberOfMCUs) * hi) + this.bx; - this.Data.Block = decoder.ProgCoeffs[compIndex][blockIndex]; + this.data.Block = decoder.ProgCoeffs[compIndex][blockIndex]; } else { - this.Data.Block.Clear(); + this.data.Block.Clear(); } this.ProcessBlockImpl(decoder, i, compIndex, hi); @@ -330,14 +223,19 @@ namespace ImageSharp.Formats.Jpg this.ResetDc(); // Reset the progressive decoder state, as per section G.1.2.2. - decoder.EobRun = 0; + this.eobRun = 0; } } // for mx } } - + + private void ResetDc() + { + Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); + } + /// /// The implementation part of as an instance method. /// @@ -368,7 +266,7 @@ namespace ImageSharp.Formats.Jpg for (int i = 0; i < this.componentScanCount; i++) { - this.ProcessScanImpl(decoder, i, ref this.Pointers.Scan[i], ref totalHv); + this.ProcessScanImpl(decoder, i, ref this.pointers.Scan[i], ref totalHv); } // Section B.2.3 states that if there is more than one component then the @@ -414,7 +312,7 @@ namespace ImageSharp.Formats.Jpg { for (int i = 0; i < this.componentScanCount; i++) { - int compIndex = this.Pointers.Scan[i].Index; + int compIndex = this.pointers.Scan[i].Index; if (decoder.ProgCoeffs[compIndex] == null) { int size = this.XNumberOfMCUs * this.YNumberOfMCUs * decoder.ComponentArray[compIndex].HorizontalFactor @@ -435,9 +333,9 @@ namespace ImageSharp.Formats.Jpg /// Horizontal sampling factor at the given component index private void ProcessBlockImpl(JpegDecoderCore decoder, int i, int compIndex, int hi) { - var b = this.Pointers.Block; - //var dc = this.Pointers.Dc; - int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].AcTableSelector; + var b = this.pointers.Block; + + int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.Scan[i].AcTableSelector; if (this.ah != 0) { this.Refine(decoder, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); @@ -452,22 +350,22 @@ namespace ImageSharp.Formats.Jpg // Decode the DC coefficient, as specified in section F.2.2.1. byte value = decoder.DecodeHuffman( - ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.Pointers.Scan[i].DcTableSelector]); + ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.Scan[i].DcTableSelector]); if (value > 16) { throw new ImageFormatException("Excessive DC component"); } int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); - this.Pointers.Dc[compIndex] += deltaDC; + this.pointers.Dc[compIndex] += deltaDC; // b[0] = dc[compIndex] << al; - Block8x8F.SetScalarAt(b, 0, this.Pointers.Dc[compIndex] << al); + Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[compIndex] << this.al); } - if (zig <= this.zigEnd && decoder.EobRun > 0) + if (zig <= this.zigEnd && this.eobRun > 0) { - decoder.EobRun--; + this.eobRun--; } else { @@ -488,19 +386,19 @@ namespace ImageSharp.Formats.Jpg int ac = decoder.Bits.ReceiveExtend(val1, decoder); // b[Unzig[zig]] = ac << al; - Block8x8F.SetScalarAt(b, this.Pointers.Unzig[zig], ac << this.al); + Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al); } else { if (val0 != 0x0f) { - decoder.EobRun = (ushort)(1 << val0); + this.eobRun = (ushort)(1 << val0); if (val0 != 0) { - decoder.EobRun |= (ushort)decoder.DecodeBits(val0); + this.eobRun |= (ushort)decoder.DecodeBits(val0); } - decoder.EobRun--; + this.eobRun--; break; } @@ -514,7 +412,7 @@ namespace ImageSharp.Formats.Jpg { if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0) { - // We haven't completely decoded this 8x8 block. Save the coefficients. + // We haven't completely decoded this 8x8 block. Save the coefficients. // this.ProgCoeffs[compIndex][((@by * XNumberOfMCUs) * hi) + bx] = b.Clone(); decoder.ProgCoeffs[compIndex][((this.by * this.XNumberOfMCUs) * hi) + this.bx] = *b; @@ -528,13 +426,13 @@ namespace ImageSharp.Formats.Jpg } // Dequantize, perform the inverse DCT and store the block to the image. - Block8x8F.UnZig(b, this.Pointers.QuantiazationTable, this.Pointers.Unzig); + Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); - DCT.TransformIDCT(ref *b, ref *this.Pointers.Temp1, ref *this.Pointers.Temp2); + DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); var destChannel = decoder.GetDestinationChannel(compIndex); var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); - destArea.LoadColorsFrom(this.Pointers.Temp1, this.Pointers.Temp2); + destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); } private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref Scan currentScan, ref int totalHv) @@ -561,7 +459,6 @@ namespace ImageSharp.Formats.Jpg this.ProcessComponentImpl(decoder, i, ref currentScan, ref totalHv, ref decoder.ComponentArray[compIndex]); } - private void ProcessComponentImpl( JpegDecoderCore decoder, int i, @@ -576,7 +473,7 @@ namespace ImageSharp.Formats.Jpg // into comp are unique. for (int j = 0; j < i; j++) { - if (currentScan.Index == this.Pointers.Scan[j].Index) + if (currentScan.Index == this.pointers.Scan[j].Index) { throw new ImageFormatException("Repeated component selector"); } @@ -600,12 +497,13 @@ namespace ImageSharp.Formats.Jpg /// /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// + /// The decoder instance /// The Huffman tree /// The low transform offset - /// The decoder instance private void Refine(JpegDecoderCore decoder, ref HuffmanTree h, int delta) { - Block8x8F* b = this.Pointers.Block; + Block8x8F* b = this.pointers.Block; + // Refining a DC component is trivial. if (this.zigStart == 0) { @@ -631,7 +529,7 @@ namespace ImageSharp.Formats.Jpg // Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3. int zig = this.zigStart; - if (decoder.EobRun == 0) + if (this.eobRun == 0) { for (; zig <= this.zigEnd; zig++) { @@ -646,10 +544,10 @@ namespace ImageSharp.Formats.Jpg case 0: if (val0 != 0x0f) { - decoder.EobRun = (ushort)(1 << val0); + this.eobRun = (ushort)(1 << val0); if (val0 != 0) { - decoder.EobRun |= (ushort)decoder.DecodeBits(val0); + this.eobRun |= (ushort)decoder.DecodeBits(val0); } done = true; @@ -683,14 +581,14 @@ namespace ImageSharp.Formats.Jpg if (z != 0) { // b[Unzig[zig]] = z; - Block8x8F.SetScalarAt(b, this.Pointers.Unzig[zig], z); + Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], z); } } } - if (decoder.EobRun > 0) + if (this.eobRun > 0) { - decoder.EobRun--; + this.eobRun--; this.RefineNonZeroes(decoder, zig, -1, delta); } } @@ -706,10 +604,10 @@ namespace ImageSharp.Formats.Jpg /// The private int RefineNonZeroes(JpegDecoderCore decoder, int zig, int nz, int delta) { - var b = this.Pointers.Block; + var b = this.pointers.Block; for (; zig <= this.zigEnd; zig++) { - int u = this.Pointers.Unzig[zig]; + int u = this.pointers.Unzig[zig]; float bu = Block8x8F.GetScalarAt(b, u); // TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary? @@ -744,5 +642,114 @@ namespace ImageSharp.Formats.Jpg return zig; } + + /// + /// Holds the "large" data blocks needed for computations + /// + [StructLayout(LayoutKind.Sequential)] + public struct ComputationData + { + /// + /// The main input block + /// + public Block8x8F Block; + + /// + /// Temporal block 1 to store intermediate and/or final computation results + /// + public Block8x8F Temp1; + + /// + /// Temporal block 2 to store intermediate and/or final computation results + /// + public Block8x8F Temp2; + + /// + /// The quantization table as + /// + public Block8x8F QuantiazationTable; + + /// + /// The jpeg unzig data + /// + public UnzigData Unzig; + + /// + /// The no-idea-what's this data + /// + public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents]; + + /// + /// The DC component values + /// + public fixed int Dc[JpegDecoderCore.MaxComponents]; + + /// + /// Creates and initializes a new instance + /// + /// The + public static ComputationData Create() + { + ComputationData data = default(ComputationData); + data.Unzig = UnzigData.Create(); + return data; + } + } + + /// + /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of + /// + public struct DataPointers + { + /// + /// Pointer to + /// + public Block8x8F* Block; + + /// + /// Pointer to + /// + public Block8x8F* Temp1; + + /// + /// Pointer to + /// + public Block8x8F* Temp2; + + /// + /// Pointer to + /// + public Block8x8F* QuantiazationTable; + + /// + /// Pointer to as int* + /// + public int* Unzig; + + /// + /// Pointer to as Scan* + /// + public Scan* Scan; + + /// + /// Pointer to + /// + public int* Dc; + + /// + /// Initializes a new instance of the struct. + /// + /// The pointer pointing to + public DataPointers(ComputationData* basePtr) + { + this.Block = &basePtr->Block; + this.Temp1 = &basePtr->Temp1; + this.Temp2 = &basePtr->Temp2; + this.QuantiazationTable = &basePtr->QuantiazationTable; + this.Unzig = basePtr->Unzig.Data; + this.Scan = (Scan*)basePtr->ScanData; + this.Dc = basePtr->Dc; + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs index a140d989c..799c3cc31 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Scan.cs @@ -1,4 +1,9 @@ -namespace ImageSharp.Formats.Jpg +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg { using System.Runtime.InteropServices; diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 541515cb4..a792a1991 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -21,30 +21,33 @@ namespace ImageSharp.Formats /// public const int MaxComponents = 4; + // Complex value type field + mutable + available to other classes == the field MUST NOT be private :P +#pragma warning disable SA1401 // FieldsMustBePrivate /// - /// The App14 marker color-space + /// Holds the unprocessed bits that have been taken from the byte-stream. /// - private byte adobeTransform; + public Bits Bits; /// - /// Whether the image is in CMYK format with an App14 marker + /// The byte buffer. /// - private bool adobeTransformValid; + public Bytes Bytes; +#pragma warning restore SA401 /// - /// Holds the unprocessed bits that have been taken from the byte-stream. + /// The maximum number of quantization tables /// - public Bits Bits; + private const int MaxTq = 3; /// - /// The byte buffer. + /// The App14 marker color-space /// - public Bytes Bytes; - + private byte adobeTransform; + /// - /// End-of-Band run, specified in section G.1.2.2. + /// Whether the image is in CMYK format with an App14 marker /// - public ushort EobRun; + private bool adobeTransformValid; /// /// The black image to decode to. @@ -60,17 +63,12 @@ namespace ImageSharp.Formats /// The horizontal resolution. Calculated if the image has a JFIF header. /// private short horizontalResolution; - - /// - /// The maximum number of quantization tables - /// - private const int MaxTq = 3; - + /// /// Whether the image has a JFIF header /// private bool isJfif; - + /// /// The vertical resolution. Calculated if the image has a JFIF header. /// @@ -112,43 +110,44 @@ namespace ImageSharp.Formats MissingFF00 } - /// - /// The component array + /// Gets the component array /// public Component[] ComponentArray { get; } /// - /// The huffman trees + /// Gets the huffman trees /// public HuffmanTree[] HuffmanTrees { get; } /// - /// Saved state between progressive-mode scans. + /// Gets the saved state between progressive-mode scans. /// public Block8x8F[][] ProgCoeffs { get; } /// - /// Quantization tables, in zigzag order. + /// Gets the quantization tables, in zigzag order. /// public Block8x8F[] QuantizationTables { get; } /// - /// A temporary buffer for holding pixels + /// Gets the temporary buffer for holding pixel (and other?) data /// // TODO: the usage rules of this buffer seem to be unclean + need to consider stack-allocating it for perf public byte[] Temp { get; } + /// - /// The number of color components within the image. + /// Gets the number of color components within the image. /// public int ComponentCount { get; private set; } + /// - /// The image height + /// Gets the image height /// public int ImageHeight { get; private set; } /// - /// The image width + /// Gets the image width /// public int ImageWidth { get; private set; } @@ -156,16 +155,17 @@ namespace ImageSharp.Formats /// Gets the input stream. /// public Stream InputStream { get; private set; } + /// - /// Whether the image is interlaced (progressive) + /// Gets a value indicating whether the image is interlaced (progressive) /// public bool IsProgressive { get; private set; } /// - /// The restart interval + /// Gets the restart interval /// public int RestartInterval { get; private set; } - + /// /// Decodes the image from the specified this._stream and sets /// the data to image. @@ -407,18 +407,39 @@ namespace ImageSharp.Formats /// /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal byte ReadByte() + public byte ReadByte() { return this.Bytes.ReadByte(this.InputStream); } + /// + /// Decodes a single bit + /// + /// The + public bool DecodeBit() + { + if (this.Bits.UnreadBits == 0) + { + ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); + if (errorCode != ErrorCodes.NoError) + { + throw new MissingFF00Exception(); + } + } + + bool ret = (this.Bits.Accumulator & this.Bits.Mask) != 0; + this.Bits.UnreadBits--; + this.Bits.Mask >>= 1; + return ret; + } + /// /// Reads exactly length bytes into data. It does not care about byte stuffing. /// /// The data to write to. /// The offset in the source buffer /// The number of bytes to read - internal void ReadFull(byte[] data, int offset, int length) + public void ReadFull(byte[] data, int offset, int length) { // Unread the overshot bytes, if any. if (this.Bytes.UnreadableBytes != 0) @@ -451,6 +472,125 @@ namespace ImageSharp.Formats } } + /// + /// Decodes the given number of bits + /// + /// The number of bits to decode. + /// The + public uint DecodeBits(int count) + { + if (this.Bits.UnreadBits < count) + { + ErrorCodes errorCode = this.Bits.EnsureNBits(count, this); + if (errorCode != ErrorCodes.NoError) + { + throw new MissingFF00Exception(); + } + } + + uint ret = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); + ret = (uint)(ret & ((1 << count) - 1)); + this.Bits.UnreadBits -= count; + this.Bits.Mask >>= count; + return ret; + } + + /// + /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. + /// + /// The huffman value + /// The + public byte DecodeHuffman(ref HuffmanTree huffmanTree) + { + // Copy stuff to the stack: + if (huffmanTree.Length == 0) + { + throw new ImageFormatException("Uninitialized Huffman table"); + } + + if (this.Bits.UnreadBits < 8) + { + ErrorCodes errorCode = this.Bits.EnsureNBits(8, this); + + if (errorCode == ErrorCodes.NoError) + { + ushort v = + huffmanTree.Lut[(this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSize)) & 0xff]; + + if (v != 0) + { + byte n = (byte)((v & 0xff) - 1); + this.Bits.UnreadBits -= n; + this.Bits.Mask >>= n; + return (byte)(v >> 8); + } + } + else + { + this.UnreadByteStuffedByte(); + } + } + + int code = 0; + for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) + { + if (this.Bits.UnreadBits == 0) + { + ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); + if (errorCode != ErrorCodes.NoError) + { + throw new MissingFF00Exception(); + } + } + + if ((this.Bits.Accumulator & this.Bits.Mask) != 0) + { + code |= 1; + } + + this.Bits.UnreadBits--; + this.Bits.Mask >>= 1; + + if (code <= huffmanTree.MaxCodes[i]) + { + return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]]; + } + + code <<= 1; + } + + throw new ImageFormatException("Bad Huffman code"); + } + + /// + /// Gets the representing the channel at a given component index + /// + /// The component index + /// The of the channel + public JpegPixelArea GetDestinationChannel(int compIndex) + { + if (this.ComponentCount == 1) + { + return this.grayImage; + } + else + { + switch (compIndex) + { + case 0: + return this.ycbcrImage.YChannel; + case 1: + return this.ycbcrImage.CbChannel; + case 2: + return this.ycbcrImage.CrChannel; + case 3: + return this.blackImage; + default: + throw new ImageFormatException("Too many components"); + } + } + } + /// /// Optimized method to pack bytes to the image from the YCbCr color space. /// This is faster than implicit casting as it avoids double packing. @@ -684,141 +824,6 @@ namespace ImageSharp.Formats this.AssignResolution(image); } - /// - /// Decodes a single bit - /// - /// The - internal bool DecodeBit() - { - if (this.Bits.UnreadBits == 0) - { - ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } - } - - bool ret = (this.Bits.Accumulator & this.Bits.Mask) != 0; - this.Bits.UnreadBits--; - this.Bits.Mask >>= 1; - return ret; - } - - /// - /// Decodes the given number of bits - /// - /// The number of bits to decode. - /// The - internal uint DecodeBits(int count) - { - if (this.Bits.UnreadBits < count) - { - ErrorCodes errorCode = this.Bits.EnsureNBits(count, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } - } - - uint ret = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); - ret = (uint)(ret & ((1 << count) - 1)); - this.Bits.UnreadBits -= count; - this.Bits.Mask >>= count; - return ret; - } - - /// - /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. - /// - /// The huffman value - /// The - internal byte DecodeHuffman(ref HuffmanTree huffmanTree) - { - // Copy stuff to the stack: - if (huffmanTree.Length == 0) - { - throw new ImageFormatException("Uninitialized Huffman table"); - } - - if (this.Bits.UnreadBits < 8) - { - ErrorCodes errorCode = this.Bits.EnsureNBits(8, this); - - if (errorCode == ErrorCodes.NoError) - { - ushort v = - huffmanTree.Lut[(this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSize)) & 0xff]; - - if (v != 0) - { - byte n = (byte)((v & 0xff) - 1); - this.Bits.UnreadBits -= n; - this.Bits.Mask >>= n; - return (byte)(v >> 8); - } - } - else - { - this.UnreadByteStuffedByte(); - } - } - - int code = 0; - for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) - { - if (this.Bits.UnreadBits == 0) - { - ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } - } - - if ((this.Bits.Accumulator & this.Bits.Mask) != 0) - { - code |= 1; - } - - this.Bits.UnreadBits--; - this.Bits.Mask >>= 1; - - if (code <= huffmanTree.MaxCodes[i]) - { - return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]]; - } - - code <<= 1; - } - - throw new ImageFormatException("Bad Huffman code"); - } - - internal JpegPixelArea GetDestinationChannel(int compIndex) - { - if (this.ComponentCount == 1) - { - return this.grayImage; - } - else - { - switch (compIndex) - { - case 0: - return this.ycbcrImage.YChannel; - case 1: - return this.ycbcrImage.CbChannel; - case 2: - return this.ycbcrImage.CrChannel; - case 3: - return this.blackImage; - default: - throw new ImageFormatException("Too many components"); - } - } - } - /// /// Returns a value indicating whether the image in an RGB image. /// diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index cd0ec0af4..59dc5ce39 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -420,7 +420,7 @@ namespace ImageSharp.Formats private void Encode444(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: Need a JpegEncoderScanProcessor struct to encapsulate all this mess: + // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default(Block8x8F); Block8x8F cb = default(Block8x8F); Block8x8F cr = default(Block8x8F); @@ -759,7 +759,7 @@ namespace ImageSharp.Formats private void WriteStartOfScan(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: This method should be the entry point for a JpegEncoderScanProcessor struct + // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: We should allow grayscale writing. this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); @@ -786,7 +786,7 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: Need a JpegEncoderScanProcessor struct to encapsulate all this mess: + // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default(Block8x8F); BlockQuad cb = default(BlockQuad); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 9ac6386b1..6736548e6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -433,21 +433,5 @@ namespace ImageSharp.Tests Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); } - - [Fact] - public unsafe void Copy_FromHeap() - { - Block8x8F[] blox = new Block8x8F[1]; - - blox[0].LoadFrom(Create8x8FloatData()); - Block8x8F clone = default(Block8x8F); - - fixed (Block8x8F* p = &blox[0]) - { - Block8x8F.Copy(&clone, p); - } - - Assert.Equal(blox[0], clone); - } } } \ No newline at end of file From 74c3517b97f2858ae9dfc72078c5c357354fdc2c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 29 Dec 2016 03:07:13 +0100 Subject: [PATCH 22/24] removed CleanPooler --- .../Jpg/Components/Decoder/JpegPixelArea.cs | 7 +++-- .../Jpg/Components/Decoder/YCbCrImage.cs | 15 ++++++---- .../Formats/Jpg/Utils/CleanPooler.cs | 30 ------------------- 3 files changed, 14 insertions(+), 38 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs index 29025dc64..916209289 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs @@ -4,6 +4,7 @@ // namespace ImageSharp.Formats.Jpg { + using System.Buffers; using System.Runtime.CompilerServices; /// @@ -75,7 +76,7 @@ namespace ImageSharp.Formats.Jpg public static JpegPixelArea CreatePooled(int width, int height) { int size = width * height; - var pixels = CleanPooler.RentCleanArray(size); + var pixels = BytePool.Rent(size); return new JpegPixelArea(pixels, width, 0); } @@ -89,10 +90,12 @@ namespace ImageSharp.Formats.Jpg return; } - CleanPooler.ReturnArray(this.Pixels); + BytePool.Return(this.Pixels); this.Pixels = null; } + private static ArrayPool BytePool => ArrayPool.Shared; + /// /// Gets the subarea that belongs to the Block8x8 defined by block indices /// diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs index f842a295c..61308ee2f 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Formats.Jpg { using System; + using System.Buffers; /// /// Represents an image made up of three color components (luminance, blue chroma, red chroma) @@ -21,9 +22,9 @@ namespace ImageSharp.Formats.Jpg { int cw, ch; YCbCrSize(width, height, ratio, out cw, out ch); - this.YPixels = CleanPooler.RentCleanArray(width * height); - this.CbPixels = CleanPooler.RentCleanArray(cw * ch); - this.CrPixels = CleanPooler.RentCleanArray(cw * ch); + this.YPixels = BytePool.Rent(width * height); + this.CbPixels = BytePool.Rent(cw * ch); + this.CrPixels = BytePool.Rent(cw * ch); this.Ratio = ratio; this.YOffset = 0; this.COffset = 0; @@ -67,6 +68,8 @@ namespace ImageSharp.Formats.Jpg YCbCrSubsampleRatio410, } + private static ArrayPool BytePool => ArrayPool.Shared; + /// /// Gets an offseted to the Cb channel /// @@ -128,9 +131,9 @@ namespace ImageSharp.Formats.Jpg /// public void Dispose() { - CleanPooler.ReturnArray(this.YPixels); - CleanPooler.ReturnArray(this.CrPixels); - CleanPooler.ReturnArray(this.CbPixels); + BytePool.Return(this.YPixels); + BytePool.Return(this.CrPixels); + BytePool.Return(this.CbPixels); } /// diff --git a/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs b/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs deleted file mode 100644 index 7369d658f..000000000 --- a/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageSharp.Formats -{ - using System.Buffers; - - /// - /// Wraps to always provide arrays initialized with default(T) - /// - /// The element type - internal class CleanPooler - { - private static readonly ArrayPool Pool = ArrayPool.Create(); - - /// - /// Rents a clean array - /// - /// The minimum array length - /// A clean array of T - public static T[] RentCleanArray(int minimumLength) => Pool.Rent(minimumLength); - - /// - /// Retursn array to the pool - /// - /// The array - public static void ReturnArray(T[] array) => Pool.Return(array, true); - } -} \ No newline at end of file From d9d8ee6445a0fd07ccbcefd86d98122c0be9717b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 29 Dec 2016 03:07:38 +0100 Subject: [PATCH 23/24] simplified YCbCrImage --- .../Jpg/Components/Decoder/YCbCrImage.cs | 153 +++++++----------- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 30 ++-- .../ImageSharp.Tests/Formats/Jpg/JpegTests.cs | 8 +- .../Formats/Jpg/YCbCrImageTests.cs | 65 ++++++++ 4 files changed, 143 insertions(+), 113 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs index 61308ee2f..6f7140fa2 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs @@ -8,62 +8,79 @@ namespace ImageSharp.Formats.Jpg using System.Buffers; /// - /// Represents an image made up of three color components (luminance, blue chroma, red chroma) + /// Represents an image made up of three color components (luminance, blue chroma, red chroma) /// internal class YCbCrImage : IDisposable { + // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P +#pragma warning disable SA1401 // FieldsMustBePrivate /// - /// Initializes a new instance of the class. + /// Gets the luminance components channel as . + /// + public JpegPixelArea YChannel; + + /// + /// Gets the blue chroma components channel as . + /// + public JpegPixelArea CbChannel; + + /// + /// Gets an offseted to the Cr channel + /// + public JpegPixelArea CrChannel; +#pragma warning restore SA1401 + + /// + /// Initializes a new instance of the class. /// /// The width. /// The height. /// The ratio. public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio) { - int cw, ch; - YCbCrSize(width, height, ratio, out cw, out ch); - this.YPixels = BytePool.Rent(width * height); - this.CbPixels = BytePool.Rent(cw * ch); - this.CrPixels = BytePool.Rent(cw * ch); + Size cSize = CalculateChrominanceSize(width, height, ratio); + this.Ratio = ratio; - this.YOffset = 0; - this.COffset = 0; this.YStride = width; - this.CStride = cw; + this.CStride = cSize.Width; + + this.YChannel = JpegPixelArea.CreatePooled(width, height); + this.CbChannel = JpegPixelArea.CreatePooled(cSize.Width, cSize.Height); + this.CrChannel = JpegPixelArea.CreatePooled(cSize.Width, cSize.Height); } /// - /// Provides enumeration of the various available subsample ratios. + /// Provides enumeration of the various available subsample ratios. /// public enum YCbCrSubsampleRatio { /// - /// YCbCrSubsampleRatio444 + /// YCbCrSubsampleRatio444 /// YCbCrSubsampleRatio444, /// - /// YCbCrSubsampleRatio422 + /// YCbCrSubsampleRatio422 /// YCbCrSubsampleRatio422, /// - /// YCbCrSubsampleRatio420 + /// YCbCrSubsampleRatio420 /// YCbCrSubsampleRatio420, /// - /// YCbCrSubsampleRatio440 + /// YCbCrSubsampleRatio440 /// YCbCrSubsampleRatio440, /// - /// YCbCrSubsampleRatio411 + /// YCbCrSubsampleRatio411 /// YCbCrSubsampleRatio411, /// - /// YCbCrSubsampleRatio410 + /// YCbCrSubsampleRatio410 /// YCbCrSubsampleRatio410, } @@ -71,77 +88,37 @@ namespace ImageSharp.Formats.Jpg private static ArrayPool BytePool => ArrayPool.Shared; /// - /// Gets an offseted to the Cb channel - /// - public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset); - - /// - /// Gets the blue chroma components channel. - /// - public byte[] CbPixels { get; } - - /// - /// Gets the index of the first element of red or blue chroma. - /// - public int COffset { get; } - - /// - /// Gets an offseted to the Cr channel + /// Gets the Y slice index delta between vertically adjacent pixels. /// - public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset); - - /// - /// Gets the red chroma components channel. - /// - public byte[] CrPixels { get; } + public int YStride { get; } /// - /// Gets the red and blue chroma slice index delta between vertically adjacent pixels - /// that map to separate chroma samples. + /// Gets the red and blue chroma slice index delta between vertically adjacent pixels + /// that map to separate chroma samples. /// public int CStride { get; } /// - /// Gets or sets the subsampling ratio. + /// Gets or sets the subsampling ratio. /// public YCbCrSubsampleRatio Ratio { get; set; } /// - /// Gets an offseted to the Y channel - /// - public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset); - - /// - /// Gets the index of the first luminance element. - /// - public int YOffset { get; } - - /// - /// Gets the luminance components channel. - /// - public byte[] YPixels { get; } - - /// - /// Gets the Y slice index delta between vertically adjacent pixels. - /// - public int YStride { get; } - - /// - /// Disposes the returning rented arrays to the pools. + /// Disposes the returning rented arrays to the pools. /// public void Dispose() { - BytePool.Return(this.YPixels); - BytePool.Return(this.CrPixels); - BytePool.Return(this.CbPixels); + this.YChannel.ReturnPooled(); + this.CbChannel.ReturnPooled(); + this.CrChannel.ReturnPooled(); } /// - /// Returns the offset of the first chroma component at the given row + /// Returns the offset of the first chroma component at the given row /// /// The row number. /// - /// The . + /// The . /// public int GetRowCOffset(int y) { @@ -163,11 +140,11 @@ namespace ImageSharp.Formats.Jpg } /// - /// Returns the offset of the first luminance component at the given row + /// Returns the offset of the first luminance component at the given row /// /// The row number. /// - /// The . + /// The . /// public int GetRowYOffset(int y) { @@ -175,48 +152,32 @@ namespace ImageSharp.Formats.Jpg } /// - /// Returns the height and width of the chroma components + /// Returns the height and width of the chroma components /// /// The width. /// The height. /// The subsampling ratio. - /// The chroma width. - /// The chroma height. - private static void YCbCrSize( + /// The of the chrominance channel + internal static Size CalculateChrominanceSize( int width, int height, - YCbCrSubsampleRatio ratio, - out int chromaWidth, - out int chromaHeight) + YCbCrSubsampleRatio ratio) { switch (ratio) { case YCbCrSubsampleRatio.YCbCrSubsampleRatio422: - chromaWidth = (width + 1) / 2; - chromaHeight = height; - break; + return new Size((width + 1) / 2, height); case YCbCrSubsampleRatio.YCbCrSubsampleRatio420: - chromaWidth = (width + 1) / 2; - chromaHeight = (height + 1) / 2; - break; + return new Size((width + 1) / 2, (height + 1) / 2); case YCbCrSubsampleRatio.YCbCrSubsampleRatio440: - chromaWidth = width; - chromaHeight = (height + 1) / 2; - break; + return new Size(width, (height + 1) / 2); case YCbCrSubsampleRatio.YCbCrSubsampleRatio411: - chromaWidth = (width + 3) / 4; - chromaHeight = height; - break; + return new Size((width + 3) / 4, height); case YCbCrSubsampleRatio.YCbCrSubsampleRatio410: - chromaWidth = (width + 3) / 4; - chromaHeight = (height + 1) / 2; - break; + return new Size((width + 3) / 4, (height + 1) / 2); default: - // Default to 4:4:4 subsampling. - chromaWidth = width; - chromaHeight = height; - break; + return new Size(width, height); } } } diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index a792a1991..284ae807b 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Formats /// public const int MaxComponents = 4; - // Complex value type field + mutable + available to other classes == the field MUST NOT be private :P + // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P #pragma warning disable SA1401 // FieldsMustBePrivate /// /// Holds the unprocessed bits that have been taken from the byte-stream. @@ -650,14 +650,15 @@ namespace ImageSharp.Formats height, y => { + // TODO: Simplify + optimize + share duplicate code across converter methods int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); for (int x = 0; x < width; x++) { - byte cyan = this.ycbcrImage.YPixels[yo + x]; - byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)]; + byte cyan = this.ycbcrImage.YChannel.Pixels[yo + x]; + byte magenta = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; + byte yellow = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); @@ -725,14 +726,15 @@ namespace ImageSharp.Formats Bootstrapper.ParallelOptions, y => { + // TODO: Simplify + optimize + share duplicate code across converter methods int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); for (int x = 0; x < width; x++) { - byte red = this.ycbcrImage.YPixels[yo + x]; - byte green = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte blue = this.ycbcrImage.CrPixels[co + (x / scale)]; + byte red = this.ycbcrImage.YChannel.Pixels[yo + x]; + byte green = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; + byte blue = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); packed.PackFromBytes(red, green, blue, 255); @@ -765,14 +767,15 @@ namespace ImageSharp.Formats Bootstrapper.ParallelOptions, y => { + // TODO: Simplify + optimize + share duplicate code across converter methods int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); for (int x = 0; x < width; x++) { - byte yy = this.ycbcrImage.YPixels[yo + x]; - byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; + byte yy = this.ycbcrImage.YChannel.Pixels[yo + x]; + byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); PackYcbCr(ref packed, yy, cb, cr); @@ -805,14 +808,15 @@ namespace ImageSharp.Formats height, y => { + // TODO: Simplify + optimize + share duplicate code across converter methods int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); for (int x = 0; x < width; x++) { - byte yy = this.ycbcrImage.YPixels[yo + x]; - byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; - byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; + byte yy = this.ycbcrImage.YChannel.Pixels[yo + x]; + byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; + byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); this.PackYcck(ref packed, yy, cb, cr, x, y); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 81156d970..53c44d836 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -94,7 +94,7 @@ namespace ImageSharp.Tests Image img = new Image(bytes); }, $"Decode {fileName}"); - + } //[Theory] // Benchmark, enable manually @@ -133,7 +133,7 @@ namespace ImageSharp.Tests for (int j = 0; j < 10; j++) { Vector4 v = new Vector4(i/10f, j/10f, 0, 1); - + TColor color = default(TColor); color.PackFromVector4(v); @@ -171,11 +171,11 @@ namespace ImageSharp.Tests [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] - public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) + 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); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs new file mode 100644 index 000000000..b90973a9c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs @@ -0,0 +1,65 @@ +namespace ImageSharp.Tests +{ + using ImageSharp.Formats.Jpg; + + using Xunit; + using Xunit.Abstractions; + + public class YCbCrImageTests + { + public YCbCrImageTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + private void PrintChannel(string name, JpegPixelArea channel) + { + this.Output.WriteLine($"{name}: Stride={channel.Stride}"); + } + + [Theory] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4, 2)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4, 1)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2, 2)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2, 1)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1, 2)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1, 1)] + public void CalculateChrominanceSize(int ratioValue, int expectedDivX, int expectedDivY) + { + YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue; + + //this.Output.WriteLine($"RATIO: {ratio}"); + Size size = YCbCrImage.CalculateChrominanceSize(400, 400, ratio); + //this.Output.WriteLine($"Ch Size: {size}"); + + Assert.Equal(new Size(400/expectedDivX, 400/expectedDivY), size); + } + + [Theory] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1)] + [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1)] + public void Create(int ratioValue, int expectedCStrideDiv) + { + YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue; + + this.Output.WriteLine($"RATIO: {ratio}"); + + var img = new YCbCrImage(400, 400, ratio); + + //this.PrintChannel("Y", img.YChannel); + //this.PrintChannel("Cb", img.CbChannel); + //this.PrintChannel("Cr", img.CrChannel); + + Assert.Equal(img.YChannel.Stride, 400); + Assert.Equal(img.CbChannel.Stride, 400 / expectedCStrideDiv); + Assert.Equal(img.CrChannel.Stride, 400 / expectedCStrideDiv); + } + + } +} \ No newline at end of file From 7b24324fafb8f7c1dc1e56a1c625cdcd90ba56b5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 29 Dec 2016 03:11:44 +0100 Subject: [PATCH 24/24] StyleCop --- .../Formats/Jpg/Components/Decoder/JpegPixelArea.cs | 4 ++-- src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs index 916209289..9fe6fecec 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs @@ -50,6 +50,8 @@ namespace ImageSharp.Formats.Jpg /// public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset); + private static ArrayPool BytePool => ArrayPool.Shared; + /// /// Returns the pixel at (x, y) /// @@ -94,8 +96,6 @@ namespace ImageSharp.Formats.Jpg this.Pixels = null; } - private static ArrayPool BytePool => ArrayPool.Shared; - /// /// Gets the subarea that belongs to the Block8x8 defined by block indices /// diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs index 6f7140fa2..a5ca9796b 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs @@ -85,8 +85,6 @@ namespace ImageSharp.Formats.Jpg YCbCrSubsampleRatio410, } - private static ArrayPool BytePool => ArrayPool.Shared; - /// /// Gets the Y slice index delta between vertically adjacent pixels. ///