From 960145b8dfe419fe96fefba19f699e3bf06bc07b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 16 Jun 2021 03:20:36 +0300 Subject: [PATCH 01/18] Added comments Added comments to the huffman spec --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 4 ++++ .../Formats/Jpeg/Components/Encoder/HuffmanSpec.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 860a9c323..10fdfeea7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -306,6 +306,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { byte b = (byte)(bits >> 24); this.emitBuffer[this.emitLen++] = b; + + // Adding stuff byte + // This is because by JPEG standard scan data can contain JPEG markers (indicated by the 0xFF byte, followed by a non-zero byte) + // Considering this every 0xFF byte must be followed by 0x00 padding byte to signal that this is not a marker if (b == byte.MaxValue) { this.emitBuffer[this.emitLen++] = byte.MinValue; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index f9c16c5be..1f9899562 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + + // Luminance AC. new HuffmanSpec( new byte[] { @@ -60,6 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa }), + + // Chrominance DC. new HuffmanSpec( new byte[] { @@ -132,4 +136,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.Values = values; } } -} \ No newline at end of file +} From 10b4a59c7c0c147f43b1b22ebf980d8e191f2d13 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 16 Jun 2021 18:16:40 +0300 Subject: [PATCH 02/18] Added debug methods Fixed System namespace usage --- .../Components/Encoder/HuffmanScanEncoder.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 10fdfeea7..9650a3db4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using SixLabors.ImageSharp.Memory; @@ -427,5 +428,53 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder return Numerics.Log2(value << 1); #endif } + + // !!! DO NOT DELETE THIS !!! until custom huffman tables are supported - this is very handy for debugging + /// + /// Prints given huffman codes to the System.Console for debugging puroses. + /// + /// Codes array. + /// Custom title to print. + /// Flag indicating if all codes should be printed. + /// Indicates which number base will be used to print codes + private static void PrintHuffmanSummary(int[] codes, string title, bool printCode, int codePrintBase = 2) + { + System.Console.WriteLine(title); + System.Console.WriteLine($"Codes count: {codes.Length}"); + + // This is possible if custom tree is provided, especially for per-image optimized tree + if (codes.Length == 0) + { + return; + } + + // Min + int min = codes.Min(); + int min_len = min >> 24; + string min_code = System.Convert.ToString(min & ((1 << 24) - 1), codePrintBase); + + // Max + int max = codes.Max(); + int max_len = max >> 24; + string max_code = System.Convert.ToString(max & ((1 << 24) - 1), codePrintBase); + + System.Console.WriteLine($"Min code: {min_code}, len: {min_len} \nMax code: {max_code}, len: {max_len}"); + + // Printing codes + if (printCode) + { + PrintHuffmanCodes(codes, codePrintBase); + } + } + + private static void PrintHuffmanCodes(int[] codes, int codePrintBase) + { + for (int i = 0; i < codes.Length; i++) + { + int huffCode = codes[i]; + string code = System.Convert.ToString(huffCode & ((1 << 24) - 1), codePrintBase); + System.Console.WriteLine($"\t{code}"); + } + } } } From 05f222e7047d55d97fdddf0509491af7bbd0d84e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 16 Jun 2021 21:50:14 +0300 Subject: [PATCH 03/18] Huffman tables are now injected rather than taken from some static variable --- .../Components/Encoder/HuffmanScanEncoder.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 9650a3db4..36f97a23d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -12,6 +12,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { internal class HuffmanScanEncoder { + private HuffmanLut[] huffmanTables; + /// /// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count). /// @@ -65,6 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public void Encode444(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming @@ -123,6 +127,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public void Encode420(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming @@ -188,6 +194,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public void EncodeGrayscale(Image pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming @@ -247,10 +255,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int dc = (int)refTemp2[0]; // Emit the DC delta. - this.EmitHuffRLE((2 * (int)index) + 0, 0, dc - prevDC); + this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC); // Emit the AC components. - int h = (2 * (int)index) + 1; + int[] huffmanTable = this.huffmanTables[(2 * (int)index) + 1].Values; + int runLength = 0; for (int zig = 1; zig < Block8x8F.Size; zig++) @@ -265,18 +274,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { while (runLength > 15) { - this.EmitHuff(h, 0xf0); + this.EmitHuff(huffmanTable, 0xf0); runLength -= 16; } - this.EmitHuffRLE(h, runLength, ac); + this.EmitHuffRLE(huffmanTable, runLength, ac); runLength = 0; } } if (runLength > 0) { - this.EmitHuff(h, 0x00); + this.EmitHuff(huffmanTable, 0x00); } return dc; @@ -339,23 +348,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Emits the given value with the given Huffman encoder. /// - /// The index of the Huffman encoder + /// Compiled Huffman spec values. /// The value to encode. [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuff(int index, int value) + private void EmitHuff(int[] table, int value) { - int x = HuffmanLut.TheHuffmanLut[index].Values[value]; + int x = table[value]; this.Emit(x & ((1 << 24) - 1), x >> 24); } /// /// Emits a run of runLength copies of value encoded with the given Huffman encoder. /// - /// The index of the Huffman encoder + /// Compiled Huffman spec values. /// The number of copies to encode. /// The value to encode. [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuffRLE(int index, int runLength, int value) + private void EmitHuffRLE(int[] table, int runLength, int value) { int a = value; int b = value; @@ -367,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int bt = GetHuffmanEncodingLength((uint)a); - this.EmitHuff(index, (runLength << 4) | bt); + this.EmitHuff(table, (runLength << 4) | bt); if (bt > 0) { this.Emit(b & ((1 << bt) - 1), bt); From 3eb6d6d1d620eeed6adced59584c137e4fb48323 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 17 Jun 2021 12:19:09 +0300 Subject: [PATCH 04/18] Simplified huffman encoding via compiled tree Squash squash --- .../Formats/Jpeg/Components/Encoder/HuffmanLut.cs | 8 ++++---- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 3 ++- .../Formats/Jpeg/Components/Encoder/HuffmanSpec.cs | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index bc6c8c6cc..ec77bf87d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -5,8 +5,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// /// A compiled look-up table representation of a huffmanSpec. - /// Each value maps to a uint32 of which the 8 most significant bits hold the - /// codeword size in bits and the 24 least significant bits hold the codeword. + /// Each value maps to a int32 of which the 24 most significant bits hold the + /// codeword in bits and the 8 least significant bits hold the codeword size. /// The maximum codeword size is 16 bits. /// internal readonly struct HuffmanLut @@ -51,10 +51,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder for (int i = 0; i < spec.Count.Length; i++) { - int bits = (i + 1) << 24; + int len = i + 1; for (int j = 0; j < spec.Count[i]; j++) { - this.Values[spec.Values[k]] = bits | code; + this.Values[spec.Values[k]] = len | (code << 8); code++; k++; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 36f97a23d..3f490d295 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -354,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private void EmitHuff(int[] table, int value) { int x = table[value]; - this.Emit(x & ((1 << 24) - 1), x >> 24); + this.Emit(x >> 8, x & 0xff); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 1f9899562..51364e3c7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 0, 0, 0 }, new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), // Luminance AC. new HuffmanSpec( From c3fdf990411af978c7954048239b07c85775c6ee Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 03:04:31 +0300 Subject: [PATCH 05/18] Removed redundant if check for rle emitter --- .../Components/Encoder/HuffmanScanEncoder.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 3f490d295..79ed59f6a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -256,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int dc = (int)refTemp2[0]; // Emit the DC delta. - this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC); + this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); // Emit the AC components. int[] huffmanTable = this.huffmanTables[(2 * (int)index) + 1].Values; @@ -358,6 +358,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.Emit(x >> 8, x & 0xff); } + [MethodImpl(InliningOptions.ShortMethod)] + private void EmitDirectCurrentTerm(int[] table, int value) + { + int a = value; + int b = value; + if (a < 0) + { + a = -value; + b = value - 1; + } + + int bt = GetHuffmanEncodingLength((uint)a); + + this.EmitHuff(table, bt); + if (bt > 0) + { + this.Emit(b & ((1 << bt) - 1), bt); + } + } + /// /// Emits a run of runLength copies of value encoded with the given Huffman encoder. /// @@ -378,10 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int bt = GetHuffmanEncodingLength((uint)a); this.EmitHuff(table, (runLength << 4) | bt); - if (bt > 0) - { - this.Emit(b & ((1 << bt) - 1), bt); - } + this.Emit(b & ((1 << bt) - 1), bt); } /// From 4e210d2e1951eac1f3fed3371d1887553c55a77a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 03:55:48 +0300 Subject: [PATCH 06/18] Jpeg encoder no longer codes trailing zeros, it writes EOB instead --- .../Components/Encoder/HuffmanScanEncoder.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 79ed59f6a..58f483ecc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -253,6 +253,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); + int lastValuableIndex = GetLastNonZeroElement(ref refTemp2); + int dc = (int)refTemp2[0]; // Emit the DC delta. @@ -263,7 +265,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int runLength = 0; - for (int zig = 1; zig < Block8x8F.Size; zig++) + for (int zig = 1; zig <= lastValuableIndex; zig++) { int ac = (int)refTemp2[zig]; @@ -284,7 +286,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - if (runLength > 0) + if (lastValuableIndex != 63) { this.EmitHuff(huffmanTable, 0x00); } @@ -456,7 +458,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder #endif } - // !!! DO NOT DELETE THIS !!! until custom huffman tables are supported - this is very handy for debugging + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetLastNonZeroElement(ref Block8x8F block) + { + int index = 63; + + ref float elemRef = ref Unsafe.As(ref block); + while ((int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + return index; + } + /// /// Prints given huffman codes to the System.Console for debugging puroses. /// From 8403ebc1e03cf2ca5a26b4fd94d9c0673a4df859 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 04:32:07 +0300 Subject: [PATCH 07/18] Removed obsolete debug code --- .../Components/Encoder/HuffmanScanEncoder.cs | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 58f483ecc..8457ddbf3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -471,52 +471,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder return index; } - - /// - /// Prints given huffman codes to the System.Console for debugging puroses. - /// - /// Codes array. - /// Custom title to print. - /// Flag indicating if all codes should be printed. - /// Indicates which number base will be used to print codes - private static void PrintHuffmanSummary(int[] codes, string title, bool printCode, int codePrintBase = 2) - { - System.Console.WriteLine(title); - System.Console.WriteLine($"Codes count: {codes.Length}"); - - // This is possible if custom tree is provided, especially for per-image optimized tree - if (codes.Length == 0) - { - return; - } - - // Min - int min = codes.Min(); - int min_len = min >> 24; - string min_code = System.Convert.ToString(min & ((1 << 24) - 1), codePrintBase); - - // Max - int max = codes.Max(); - int max_len = max >> 24; - string max_code = System.Convert.ToString(max & ((1 << 24) - 1), codePrintBase); - - System.Console.WriteLine($"Min code: {min_code}, len: {min_len} \nMax code: {max_code}, len: {max_len}"); - - // Printing codes - if (printCode) - { - PrintHuffmanCodes(codes, codePrintBase); - } - } - - private static void PrintHuffmanCodes(int[] codes, int codePrintBase) - { - for (int i = 0; i < codes.Length; i++) - { - int huffCode = codes[i]; - string code = System.Convert.ToString(huffCode & ((1 << 24) - 1), codePrintBase); - System.Console.WriteLine($"\t{code}"); - } - } } } From 007c52a3f2efb4f52c55c0415aa48fbfd51b3bfd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 05:02:06 +0300 Subject: [PATCH 08/18] Fixed possible out of range exception, added docs --- .../Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 8457ddbf3..43d7b07b9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -458,13 +458,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder #endif } + /// + /// Returns index of the last non-zero element in given mcu block, returns -1 if all elements are zero + /// + /// Input mcu. + /// Index of the last non-zero element. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetLastNonZeroElement(ref Block8x8F block) + internal static int GetLastNonZeroElement(ref Block8x8F block) { int index = 63; - ref float elemRef = ref Unsafe.As(ref block); - while ((int)Unsafe.Add(ref elemRef, index) == 0) + while (index > -1 && (int)Unsafe.Add(ref elemRef, index) == 0) { index--; } From 26a0f0bc6fa25087f89b7ef0d40841cab360c679 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 05:10:24 +0300 Subject: [PATCH 09/18] Fixeds docs, fixed API, fixed if check --- .../Components/Encoder/HuffmanScanEncoder.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 43d7b07b9..c610c2483 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -286,7 +286,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - if (lastValuableIndex != 63) + // if mcu block contains trailing zeros - we must write end of block (EOB) values indicating that current block is over + // this can be done for any number of trailing zeros + if (lastValuableIndex < Block8x8F.Size - 1) { this.EmitHuff(huffmanTable, 0x00); } @@ -461,14 +463,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Returns index of the last non-zero element in given mcu block, returns -1 if all elements are zero /// - /// Input mcu. + /// Return range is [1..63]. + /// Mcu block. /// Index of the last non-zero element. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int GetLastNonZeroElement(ref Block8x8F block) + private static int GetLastNonZeroElement(ref Block8x8F mcu) { - int index = 63; - ref float elemRef = ref Unsafe.As(ref block); - while (index > -1 && (int)Unsafe.Add(ref elemRef, index) == 0) + int index = Block8x8F.Size - 1; + ref float elemRef = ref Unsafe.As(ref mcu); + + // Index range is [63..0), first element is a DC value which will be emitted even if entire mcu contains only zeros + while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) { index--; } From 683a125566988a186d8deb4ecae5008d199b6644 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 06:39:20 +0300 Subject: [PATCH 10/18] Added comments --- .../Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index c610c2483..631128c8c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -261,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); // Emit the AC components. - int[] huffmanTable = this.huffmanTables[(2 * (int)index) + 1].Values; + int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; int runLength = 0; @@ -277,20 +277,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { while (runLength > 15) { - this.EmitHuff(huffmanTable, 0xf0); + this.EmitHuff(acHuffTable, 0xf0); runLength -= 16; } - this.EmitHuffRLE(huffmanTable, runLength, ac); + this.EmitHuffRLE(acHuffTable, runLength, ac); runLength = 0; } } - // if mcu block contains trailing zeros - we must write end of block (EOB) values indicating that current block is over - // this can be done for any number of trailing zeros + // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over + // this can be done for any number of trailing zeros, even when all 63 ac values are zero if (lastValuableIndex < Block8x8F.Size - 1) { - this.EmitHuff(huffmanTable, 0x00); + this.EmitHuff(acHuffTable, 0x00); } return dc; From 467a069620b6ccbd12c09d307ee8eaa594569353 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 09:38:06 +0300 Subject: [PATCH 11/18] Docs, cleanup, ready for tests --- .../Components/Encoder/HuffmanScanEncoder.cs | 78 ++++++++++++++----- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 631128c8c..e42a2f0f3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using System.Threading; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -253,18 +255,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); - int lastValuableIndex = GetLastNonZeroElement(ref refTemp2); - - int dc = (int)refTemp2[0]; - // Emit the DC delta. + int dc = (int)refTemp2[0]; this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); // Emit the AC components. int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; int runLength = 0; - + int lastValuableIndex = GetLastValuableElementIndex(ref refTemp2); for (int zig = 1; zig <= lastValuableIndex; zig++) { int ac = (int)refTemp2[zig]; @@ -288,7 +287,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over // this can be done for any number of trailing zeros, even when all 63 ac values are zero - if (lastValuableIndex < Block8x8F.Size - 1) + // (Block8x8F.Size - 1) == 63 - last index of the mcu elements + if (lastValuableIndex != Block8x8F.Size - 1) { this.EmitHuff(acHuffTable, 0x00); } @@ -433,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetHuffmanEncodingLength(uint value) + internal static int GetHuffmanEncodingLength(uint value) { DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); #if SUPPORTS_BITOPERATIONS @@ -461,24 +461,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// Returns index of the last non-zero element in given mcu block, returns -1 if all elements are zero + /// Returns index of the last non-zero element in given mcu block /// - /// Return range is [1..63]. + /// + /// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support. + /// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop. + /// This method is guaranteed to return either -1 or 0 if all elements are zero. + /// /// Mcu block. /// Index of the last non-zero element. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetLastNonZeroElement(ref Block8x8F mcu) + internal static int GetLastValuableElementIndex(ref Block8x8F mcu) { - int index = Block8x8F.Size - 1; - ref float elemRef = ref Unsafe.As(ref mcu); - - // Index range is [63..0), first element is a DC value which will be emitted even if entire mcu contains only zeros - while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) { - index--; + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + + Vector256 zero8 = Vector256.Zero; + + ref Vector256 mcuStride = ref mcu.V0; + + for (int i = 8; i >= 0; i--) + { + int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); + + // we do not know for sure if this stride contain all non-zero elements or if it has some trailing zeros + if (areEqual != equalityMask) + { + // last index in the stride, we go from the end to the start of the stride + int startIndex = i * 8; + int index = startIndex + 7; + ref float elemRef = ref Unsafe.As(ref mcu); + while (index >= startIndex && (int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } + + // this implementation will return -1 if all ac components are zero and dc are zero + return index; + } + } + + return -1; } + else +#endif + { + int index = Block8x8F.Size - 1; + ref float elemRef = ref Unsafe.As(ref mcu); + + while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) + { + index--; + } - return index; + // this implementation will return 0 if all ac components and dc are zero + return index; + } } } } From 79a1e117a360760e39e138558153c0ca4bab95f0 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 09:41:05 +0300 Subject: [PATCH 12/18] Removed redundant check --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index e42a2f0f3..f1c7cdc74 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -416,11 +416,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder if (padBitsCount != 0) { this.Emit((1 << padBitsCount) - 1, padBitsCount); - } - - // flush remaining bytes - if (this.emitLen != 0) - { this.target.Write(this.emitBuffer, 0, this.emitLen); } } From 158969501ed986a3c0365e2c3eb1a7d9ff730185 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 09:41:59 +0300 Subject: [PATCH 13/18] Small fixes --- .../Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index f1c7cdc74..a0ea14146 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -424,10 +424,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. /// /// - /// This method returns 0 for input value 0. This is done specificaly for huffman encoding + /// This is an internal operation supposed to be used only in class for jpeg encoding. /// /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] internal static int GetHuffmanEncodingLength(uint value) { DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); @@ -456,16 +456,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// Returns index of the last non-zero element in given mcu block - /// - /// + /// Returns index of the last non-zero element in given mcu block. /// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support. /// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop. /// This method is guaranteed to return either -1 or 0 if all elements are zero. + /// + /// + /// This is an internal operation supposed to be used only in class for jpeg encoding. /// /// Mcu block. /// Index of the last non-zero element. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] internal static int GetLastValuableElementIndex(ref Block8x8F mcu) { #if SUPPORTS_RUNTIME_INTRINSICS From 6d14b8205cad6a809e014077ca31fc09939521e0 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 10:23:16 +0300 Subject: [PATCH 14/18] Added tests for GetHuffmanEncodingLength --- .../Formats/Jpg/HuffmanScanEncoderTests.cs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs new file mode 100644 index 000000000..095fdef81 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics.X86; +#endif +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + [Trait("Format", "Jpg")] + public class HuffmanScanEncoderTests + { + private ITestOutputHelper Output { get; } + + public HuffmanScanEncoderTests(ITestOutputHelper output) + { + Output = output; + } + + private static int GetHuffmanEncodingLength_Reference(uint number) + { + int bits = 0; + if (number > 32767) + { + number >>= 16; + bits += 16; + } + if (number > 127) + { + number >>= 8; + bits += 8; + } + if (number > 7) + { + number >>= 4; + bits += 4; + } + if (number > 1) + { + number >>= 2; + bits += 2; + } + if (number > 0) + { + bits++; + } + return bits; + } + + [Fact] + public void GetHuffmanEncodingLength_Zero() + { + int expected = 0; + + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(0); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetHuffmanEncodingLength_Random(int seed) + { + int maxNumber = 1 << 16; + + var rng = new Random(seed); + for (int i = 0; i < 1000; i++) + { + uint number = (uint)rng.Next(0, maxNumber); + + int expected = GetHuffmanEncodingLength_Reference(number); + + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(number); + + Assert.Equal(expected, actual); + } + } + } +} From 0a6bf553e32f79f21f48574938fff9052b221950 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 11:44:59 +0300 Subject: [PATCH 15/18] Added tests for GetLastValuableElementIndex --- .../Formats/Jpg/HuffmanScanEncoderTests.cs | 163 +++++++++++++++++- 1 file changed, 158 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs index 095fdef81..b953e80b8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -2,12 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics.X86; -#endif using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -22,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public HuffmanScanEncoderTests(ITestOutputHelper output) { - Output = output; + this.Output = output; } private static int GetHuffmanEncodingLength_Reference(uint number) @@ -33,25 +29,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg number >>= 16; bits += 16; } + if (number > 127) { number >>= 8; bits += 8; } + if (number > 7) { number >>= 4; bits += 4; } + if (number > 1) { number >>= 2; bits += 2; } + if (number > 0) { bits++; } + return bits; } @@ -84,5 +85,157 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual); } } + + [Fact] + public void GetLastValuableElementIndex_AllZero() + { + static void RunTest() + { + Block8x8F data = default; + + int expectedLessThan = 1; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.True(actual < expectedLessThan); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Fact] + public void GetLastValuableElementIndex_AllNonZero() + { + static void RunTest() + { + Block8x8F data = default; + for (int i = 0; i < Block8x8F.Size; i++) + { + data[i] = 10; + } + + int expected = Block8x8F.Size - 1; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledSingle(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int setIndex = rng.Next(1, Block8x8F.Size); + data[setIndex] = rng.Next(); + + int expected = setIndex; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledPartially(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int lastIndex = rng.Next(1, Block8x8F.Size); + int fillValue = rng.Next(); + for (int dataIndex = 0; dataIndex <= lastIndex; dataIndex++) + { + data[dataIndex] = fillValue; + } + + int expected = lastIndex; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastValuableElementIndex_RandomFilledFragmented(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) + { + Block8x8F data = default; + + int fillValue = rng.Next(); + + // first filled chunk + int lastIndex1 = rng.Next(1, Block8x8F.Size / 2); + for (int dataIndex = 0; dataIndex <= lastIndex1; dataIndex++) + { + data[dataIndex] = fillValue; + } + + // second filled chunk, there might be a spot with zero(s) between first and second chunk + int lastIndex2 = rng.Next(lastIndex1 + 1, Block8x8F.Size); + for (int dataIndex = 0; dataIndex <= lastIndex2; dataIndex++) + { + data[dataIndex] = fillValue; + } + + int expected = lastIndex2; + + int actual = HuffmanScanEncoder.GetLastValuableElementIndex(ref data); + + Assert.Equal(expected, actual); + } + } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } } } From 2aff02351466a04839619370dc992e5722454bfd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 11:45:17 +0300 Subject: [PATCH 16/18] Fixed GetLastValuableElementIndex invalid indexing --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index a0ea14146..23ff2ab35 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -478,7 +478,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ref Vector256 mcuStride = ref mcu.V0; - for (int i = 8; i >= 0; i--) + for (int i = 7; i >= 0; i--) { int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); From b06fd195c354989dedd9ae5084525acea7973831 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 11:52:21 +0300 Subject: [PATCH 17/18] Added docs --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 23ff2ab35..331da275c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -15,6 +15,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { internal class HuffmanScanEncoder { + /// + /// Compiled huffman tree to encode given values. + /// + /// Yields codewords by index consisting of [run length | bitsize]. private HuffmanLut[] huffmanTables; /// From ba8f344e1317f6a3960e5f489653f1f3e66a2939 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 19 Jun 2021 13:11:47 +0300 Subject: [PATCH 18/18] Added updated benchmarks --- .../Codecs/Jpeg/EncodeJpeg.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index d472791e4..87170e8d2 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -114,21 +114,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT [AttachedDebugger] + [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT DefaultJob : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT -| Method | Quality | Mean | Error | StdDev | Ratio | RatioSD | -|---------------------------- |-------- |---------:|---------:|---------:|------:|--------:| -| 'System.Drawing Jpeg 4:2:0' | 75 | 30.60 ms | 0.496 ms | 0.464 ms | 1.00 | 0.00 | -| 'ImageSharp Jpeg 4:2:0' | 75 | 29.86 ms | 0.350 ms | 0.311 ms | 0.98 | 0.02 | -| 'ImageSharp Jpeg 4:4:4' | 75 | 45.36 ms | 0.899 ms | 1.036 ms | 1.48 | 0.05 | -| | | | | | | | -| 'System.Drawing Jpeg 4:2:0' | 90 | 34.05 ms | 0.669 ms | 0.687 ms | 1.00 | 0.00 | -| 'ImageSharp Jpeg 4:2:0' | 90 | 37.26 ms | 0.706 ms | 0.660 ms | 1.10 | 0.03 | -| 'ImageSharp Jpeg 4:4:4' | 90 | 52.54 ms | 0.579 ms | 0.514 ms | 1.55 | 0.04 | -| | | | | | | | -| 'System.Drawing Jpeg 4:2:0' | 100 | 39.36 ms | 0.267 ms | 0.237 ms | 1.00 | 0.00 | -| 'ImageSharp Jpeg 4:2:0' | 100 | 42.44 ms | 0.410 ms | 0.383 ms | 1.08 | 0.01 | -| 'ImageSharp Jpeg 4:4:4' | 100 | 70.88 ms | 0.508 ms | 0.450 ms | 1.80 | 0.02 | +| Method | Quality | Mean | Error | StdDev | Ratio | +|---------------------------- |-------- |---------:|---------:|---------:|------:| +| 'System.Drawing Jpeg 4:2:0' | 75 | 29.41 ms | 0.108 ms | 0.096 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 75 | 26.30 ms | 0.131 ms | 0.109 ms | 0.89 | +| 'ImageSharp Jpeg 4:4:4' | 75 | 36.70 ms | 0.303 ms | 0.269 ms | 1.25 | +| | | | | | | +| 'System.Drawing Jpeg 4:2:0' | 90 | 32.67 ms | 0.226 ms | 0.211 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 90 | 33.56 ms | 0.237 ms | 0.222 ms | 1.03 | +| 'ImageSharp Jpeg 4:4:4' | 90 | 44.82 ms | 0.250 ms | 0.234 ms | 1.37 | +| | | | | | | +| 'System.Drawing Jpeg 4:2:0' | 100 | 39.06 ms | 0.233 ms | 0.218 ms | 1.00 | +| 'ImageSharp Jpeg 4:2:0' | 100 | 40.23 ms | 0.225 ms | 0.277 ms | 1.03 | +| 'ImageSharp Jpeg 4:4:4' | 100 | 63.35 ms | 0.486 ms | 0.431 ms | 1.62 | */