diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentExtensions.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentExtensions.cs deleted file mode 100644 index d7fb52a790..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - /// - /// Extension methods for - /// - internal static class JpegComponentExtensions - { - /// - /// Gets a reference to the at the given row and column index from - /// - /// The - /// The column - /// The row - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static ref Block8x8 GetBlockReference(this IJpegComponent component, int column, int row) - { - return ref component.SpectralBlocks.GetRowSpan(row)[column]; - } - - /// - /// Gets a reference to the first item in a block - /// at the given row and column index from - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static ref short GetBlockDataReference(this IJpegComponent component, int column, int row) - { - ref Block8x8 blockRef = ref component.GetBlockReference(column, row); - return ref Unsafe.As(ref blockRef); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs index 39b9792ac9..ec9805309e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs @@ -1,8 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -179,10 +181,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; + int mcuRow = mcu / mcusPerLine; + // Scan out an mcu's worth of this component; that's just determined // by the basic H and V specified for the component for (int y = 0; y < v; y++) { + int blockRow = (mcuRow * v) + y; + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); for (int x = 0; x < h; x++) { if (this.eof) @@ -190,15 +196,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder return; } - int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; int blockCol = (mcuCol * h) + x; this.DecodeBlockBaseline( component, - blockRow, - blockCol, + ref blockSpan[blockCol], ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); @@ -236,6 +239,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int mcu = 0; for (int j = 0; j < h; j++) { + // TODO: Isn't blockRow == j actually? + int blockRow = mcu / w; + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + for (int i = 0; i < w; i++) { if (this.eof) @@ -243,13 +250,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder return; } - int blockRow = mcu / w; + // TODO: Isn't blockCol == i actually? int blockCol = mcu % w; this.DecodeBlockBaseline( component, - blockRow, - blockCol, + ref blockSpan[blockCol], ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); @@ -299,6 +305,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // by the basic H and V specified for the component for (int y = 0; y < v; y++) { + int mcuRow = mcu / mcusPerLine; + int blockRow = (mcuRow * v) + y; + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + for (int x = 0; x < h; x++) { if (this.eof) @@ -306,15 +316,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder return; } - int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; int blockCol = (mcuCol * h) + x; this.DecodeBlockProgressiveDC( component, - blockRow, - blockCol, + ref blockSpan[blockCol], ref dcHuffmanTable); } } @@ -351,6 +358,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int mcu = 0; for (int j = 0; j < h; j++) { + // TODO: isn't blockRow == j actually? + int blockRow = mcu / w; + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + for (int i = 0; i < w; i++) { if (this.eof) @@ -358,23 +369,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder return; } - int blockRow = mcu / w; + // TODO: isn't blockCol == i actually? int blockCol = mcu % w; + ref Block8x8 block = ref blockSpan[blockCol]; + if (this.spectralStart == 0) { this.DecodeBlockProgressiveDC( component, - blockRow, - blockCol, + ref block, ref dcHuffmanTable); } else { this.DecodeBlockProgressiveAC( component, - blockRow, - blockCol, + ref block, ref acHuffmanTable, ref fastACRef); } @@ -391,8 +402,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void DecodeBlockBaseline( JpegComponent component, - int row, - int col, + ref Block8x8 block, ref HuffmanTable dcTable, ref HuffmanTable acTable, ref short fastACRef) @@ -405,7 +415,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder JpegThrowHelper.ThrowBadHuffmanCode(); } - ref short blockDataRef = ref component.GetBlockDataReference(col, row); + ref short blockDataRef = ref Unsafe.As(ref block); int diff = t != 0 ? this.ExtendReceive(t) : 0; int dc = component.DcPredictor + diff; @@ -470,8 +480,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void DecodeBlockProgressiveDC( JpegComponent component, - int row, - int col, + ref Block8x8 block, ref HuffmanTable dcTable) { if (this.spectralEnd != 0) @@ -481,7 +490,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.CheckBits(); - ref short blockDataRef = ref component.GetBlockDataReference(col, row); + ref short blockDataRef = ref Unsafe.As(ref block); if (this.successiveHigh == 0) { @@ -506,8 +515,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void DecodeBlockProgressiveAC( JpegComponent component, - int row, - int col, + ref Block8x8 block, ref HuffmanTable acTable, ref short fastACRef) { @@ -516,7 +524,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); } - ref short blockDataRef = ref component.GetBlockDataReference(col, row); + ref short blockDataRef = ref Unsafe.As(ref block); if (this.successiveHigh == 0) { diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index e161bca05b..9292d5f7de 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -46,15 +46,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + // NOTE: + // The scaled result for very large image "TestImages.Jpeg.Issues.ExifGetString750Transform" + // is almost the same as the result for Jpeg420Exif, + // which proves that the execution time for the most common YCbCr 420 path scales linearly [Params( TestImages.Jpeg.Baseline.Lake, TestImages.Jpeg.Issues.BadRstProgressive518, - TestImages.Jpeg.Issues.ExifGetString750Transform, TestImages.Jpeg.Baseline.Jpeg420Exif )] public string TestImage { get; set; } + [GlobalSetup] public void ReadImages() { @@ -110,16 +114,16 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // RESULTS (2018 November 4): // Method | TestImage | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // ------------------------------- |-------------------------------------------- |-----------:|-----------:|----------:|-------:|---------:|----------:|---------:|---------:|------------:| - // 'Decode Jpeg - System.Drawing' | Jpg/baseline/Lake.jpg | 6.291 ms | 1.200 ms | 0.0678 ms | 1.00 | 0.00 | 62.5000 | - | - | 205.83 KB | - // 'Decode Jpeg - ImageSharp' | Jpg/baseline/Lake.jpg | 18.493 ms | 3.025 ms | 0.1709 ms | 2.94 | 0.03 | - | - | - | 19.97 KB | + // 'Decode Jpeg - System.Drawing' | Jpg/baseline/Lake.jpg | 6.117 ms | 0.3923 ms | 0.0222 ms | 1.00 | 0.00 | 62.5000 | - | - | 205.83 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/baseline/Lake.jpg | 18.126 ms | 0.6023 ms | 0.0340 ms | 2.96 | 0.01 | - | - | - | 19.97 KB | // | | | | | | | | | | | - // 'Decode Jpeg - System.Drawing' | Jpg/baseline/jpeg420exif.jpg | 16.962 ms | 1.446 ms | 0.0817 ms | 1.00 | 0.00 | 218.7500 | - | - | 757.04 KB | - // 'Decode Jpeg - ImageSharp' | Jpg/baseline/jpeg420exif.jpg | 42.105 ms | 4.496 ms | 0.2540 ms | 2.48 | 0.02 | - | - | - | 21.94 KB | + // 'Decode Jpeg - System.Drawing' | Jpg/baseline/jpeg420exif.jpg | 17.063 ms | 2.6096 ms | 0.1474 ms | 1.00 | 0.00 | 218.7500 | - | - | 757.04 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/baseline/jpeg420exif.jpg | 41.366 ms | 1.0115 ms | 0.0572 ms | 2.42 | 0.02 | - | - | - | 21.94 KB | // | | | | | | | | | | | - // 'Decode Jpeg - System.Drawing' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 432.344 ms | 89.746 ms | 5.0708 ms | 1.00 | 0.00 | 2375.0000 | - | - | 7403.76 KB | - // 'Decode Jpeg - ImageSharp' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 421.292 ms | 128.587 ms | 7.2654 ms | 0.97 | 0.02 | 125.0000 | 125.0000 | 125.0000 | 35186.98 KB | + // 'Decode Jpeg - System.Drawing' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 428.282 ms | 94.9163 ms | 5.3629 ms | 1.00 | 0.00 | 2375.0000 | - | - | 7403.76 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/issues/Issue518-Bad-RST-Progressive.jpg | 386.698 ms | 33.0065 ms | 1.8649 ms | 0.90 | 0.01 | 125.0000 | 125.0000 | 125.0000 | 35186.97 KB | // | | | | | | | | | | | - // 'Decode Jpeg - System.Drawing' | Jpg/issues/issue750-exif-tranform.jpg | 94.723 ms | 4.663 ms | 0.2635 ms | 1.00 | 0.00 | 1750.0000 | - | - | 5492.63 KB | - // 'Decode Jpeg - ImageSharp' | Jpg/issues/issue750-exif-tranform.jpg | 234.071 ms | 37.979 ms | 2.1459 ms | 2.47 | 0.02 | 312.5000 | 312.5000 | 312.5000 | 58834.45 KB | + // 'Decode Jpeg - System.Drawing' | Jpg/issues/issue750-exif-tranform.jpg | 95.192 ms | 3.1762 ms | 0.1795 ms | 1.00 | 0.00 | 1750.0000 | - | - | 5492.63 KB | + // 'Decode Jpeg - ImageSharp' | Jpg/issues/issue750-exif-tranform.jpg | 230.158 ms | 48.8128 ms | 2.7580 ms | 2.42 | 0.02 | 312.5000 | 312.5000 | 312.5000 | 58834.66 KB | } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 7acce84cea..e4fcd10c5f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -67,9 +67,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int y = 0; y < result.HeightInBlocks; y++) { + Span blockRow = c.SpectralBlocks.GetRowSpan(y); for (int x = 0; x < result.WidthInBlocks; x++) { - short[] data = c.GetBlockReference(x, y).ToArray(); + short[] data = blockRow[x].ToArray(); result.MakeBlock(data, y, x); } }