From 19e2e3d40df77f77a46f4468deb59515ffc45572 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 12 Jul 2021 20:01:09 +0300 Subject: [PATCH] Fixed new spectral tests for progressive and multi-scan images --- .../Formats/Jpg/SpectralJpegTests.cs | 62 ++++++++++++++++--- .../Jpg/Utils/LibJpegTools.ComponentData.cs | 31 ++++++++++ 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 0a457985f1..0235ebb382 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -61,7 +61,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg VerifyJpeg.SaveSpectralImage(provider, data); } - [Theory(Skip = "Temporary skipped due to new decoder core architecture")] + //[Theory(Skip = "Temporary skipped due to new decoder core architecture")] + [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void VerifySpectralCorrectness(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -86,12 +87,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); // This would parse entire image - // Due to underlying architecture, baseline interleaved jpegs would be tested inside the parsing loop - // Everything else must be checked manually after this method decoder.ParseStream(bufferedStream, scanDecoder, ct: default); - var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - this.VerifySpectralCorrectnessImpl(libJpegData, imageSharpData); + // Actual verification + this.VerifySpectralCorrectnessImpl(libJpegData, debugConverter.SpectralData); } private void VerifySpectralCorrectnessImpl( @@ -141,27 +140,74 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { private readonly SpectralConverter converter; + private JpegFrame frame; + + private LibJpegTools.SpectralData spectralData; + + private int baselineScanRowCounter; + public DebugSpectralConverter(Configuration configuration, CancellationToken cancellationToken) => this.converter = new SpectralConverter(configuration, cancellationToken); + public LibJpegTools.SpectralData SpectralData + { + get + { + // Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing + // Progressive and multi-scan images must be loaded manually + if (this.frame.Progressive || this.frame.MultiScan) + { + LibJpegTools.ComponentData[] components = this.spectralData.Components; + for (int i = 0; i < components.Length; i++) + { + components[i].LoadSpectral(this.frame.Components[i]); + } + } + + return this.spectralData; + } + } + public override void ConvertStrideBaseline() { this.converter.ConvertStrideBaseline(); // This would be called only for baseline non-interleaved images // We must test spectral strides here + LibJpegTools.ComponentData[] components = this.spectralData.Components; + for (int i = 0; i < components.Length; i++) + { + components[i].LoadSpectralStride(this.frame.Components[i].SpectralBlocks, this.baselineScanRowCounter); + } + + this.baselineScanRowCounter++; } public override void Dispose() { - this.converter?.Dispose(); - // As we are only testing spectral data we don't care about pixels // But we need to dispose allocated pixel buffer this.converter.PixelBuffer.Dispose(); + + // Converter Dispose must be called after pixel buffer disposal because pixel buffer getter can do a full scan conversion + this.converter?.Dispose(); } - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) => this.converter.InjectFrameData(frame, jpegData); + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.converter.InjectFrameData(frame, jpegData); + + this.frame = frame; + + var spectralComponents = new LibJpegTools.ComponentData[frame.ComponentCount]; + for (int i = 0; i < spectralComponents.Length; i++) + { + JpegComponent component = frame.Components[i]; + spectralComponents[i] = new LibJpegTools.ComponentData(component.WidthInBlocks, component.HeightInBlocks, component.Index); + } + + this.spectralData = new LibJpegTools.SpectralData(spectralComponents); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 6f6032ee2e..4ec9b8d69c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -56,6 +56,37 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.SpectralBlocks[x, y] = new Block8x8(data); } + public void LoadSpectralStride(Buffer2D data, int strideIndex) + { + for (int y = 0; y < data.Height; y++) + { + Span blockRow = data.GetRowSpan(y); + for (int x = 0; x < data.Width; x++) + { + short[] block = blockRow[x].ToArray(); + + // x coordinate stays the same - we load entire stride + // y coordinate is tricky as we load single stride to full buffer - offset is needed + int yOffset = strideIndex * data.Height; + this.MakeBlock(block, y + yOffset, x); + } + } + } + + public void LoadSpectral(JpegComponent c) + { + Buffer2D data = c.SpectralBlocks; + for (int y = 0; y < c.HeightInBlocks; y++) + { + Span blockRow = data.GetRowSpan(y); + for (int x = 0; x < c.WidthInBlocks; x++) + { + short[] block = blockRow[x].ToArray(); + this.MakeBlock(block, y, x); + } + } + } + public static ComponentData Load(JpegComponent c, int index) { var result = new ComponentData(