mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
23 changed files with 713 additions and 525 deletions
@ -1,181 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the execution od post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
|
|||
/// (1) Dequantization <br/>
|
|||
/// (2) IDCT <br/>
|
|||
/// (3) Color conversion form one of the <see cref="JpegColorSpace"/>-s into a <see cref="Vector4"/> buffer of RGBA values <br/>
|
|||
/// (4) Packing <see cref="Image{TPixel}"/> pixels from the <see cref="Vector4"/> buffer. <br/>
|
|||
/// These operations are executed in <see cref="NumberOfPostProcessorSteps"/> steps.
|
|||
/// <see cref="PixelRowsPerStep"/> image rows are converted in one step,
|
|||
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageFrame.Height"/>).
|
|||
/// </summary>
|
|||
internal class JpegImagePostProcessor : IDisposable |
|||
{ |
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The number of block rows to be processed in one Step.
|
|||
/// </summary>
|
|||
public const int BlockRowsPerStep = 4; |
|||
|
|||
/// <summary>
|
|||
/// The number of image pixel rows to be processed in one step.
|
|||
/// </summary>
|
|||
public const int PixelRowsPerStep = 4 * 8; |
|||
|
|||
/// <summary>
|
|||
/// Temporal buffer to store a row of colors.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<Vector4> rgbaBuffer; |
|||
|
|||
/// <summary>
|
|||
/// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
|
|||
/// </summary>
|
|||
private readonly JpegColorConverter colorConverter; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
|
|||
/// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param>
|
|||
public JpegImagePostProcessor(Configuration configuration, IRawJpegData rawJpeg) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.RawJpeg = rawJpeg; |
|||
IJpegComponent c0 = rawJpeg.Components[0]; |
|||
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; |
|||
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); |
|||
|
|||
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; |
|||
|
|||
this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length]; |
|||
for (int i = 0; i < rawJpeg.Components.Length; i++) |
|||
{ |
|||
this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]); |
|||
} |
|||
|
|||
this.rgbaBuffer = memoryAllocator.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width); |
|||
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="JpegComponentPostProcessor"/> instances.
|
|||
/// </summary>
|
|||
public JpegComponentPostProcessor[] ComponentProcessors { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="IRawJpegData"/> to be processed.
|
|||
/// </summary>
|
|||
public IRawJpegData RawJpeg { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the total number of post processor steps deduced from the height of the image and <see cref="PixelRowsPerStep"/>.
|
|||
/// </summary>
|
|||
public int NumberOfPostProcessorSteps { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the temporary buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
|
|||
/// </summary>
|
|||
public Size PostProcessorBufferSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the counter that grows by each step by <see cref="PixelRowsPerStep"/>.
|
|||
/// </summary>
|
|||
public int PixelRowCounter { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) |
|||
{ |
|||
cpp.Dispose(); |
|||
} |
|||
|
|||
this.rgbaBuffer.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Process all pixels into 'destination'. The image dimensions should match <see cref="RawJpeg"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type</typeparam>
|
|||
/// <param name="destination">The destination image</param>
|
|||
/// <param name="cancellationToken">The token to request cancellation.</param>
|
|||
public void PostProcess<TPixel>(ImageFrame<TPixel> destination, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
this.PixelRowCounter = 0; |
|||
|
|||
if (this.RawJpeg.ImageSizeInPixels != destination.Size()) |
|||
{ |
|||
throw new ArgumentException("Input image is not of the size of the processed one!"); |
|||
} |
|||
|
|||
while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
this.DoPostProcessorStep(destination); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Execute one step processing <see cref="PixelRowsPerStep"/> pixel rows into 'destination'.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type</typeparam>
|
|||
/// <param name="destination">The destination image.</param>
|
|||
public void DoPostProcessorStep<TPixel>(ImageFrame<TPixel> destination) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) |
|||
{ |
|||
cpp.CopyBlocksToColorBuffer(); |
|||
} |
|||
|
|||
this.ConvertColorsInto(destination); |
|||
|
|||
this.PixelRowCounter += PixelRowsPerStep; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert and copy <see cref="PixelRowsPerStep"/> row of colors into 'destination' starting at row <see cref="PixelRowCounter"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type</typeparam>
|
|||
/// <param name="destination">The destination image</param>
|
|||
private void ConvertColorsInto<TPixel>(ImageFrame<TPixel> destination) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); |
|||
|
|||
var buffers = new Buffer2D<float>[this.ComponentProcessors.Length]; |
|||
for (int i = 0; i < this.ComponentProcessors.Length; i++) |
|||
{ |
|||
buffers[i] = this.ComponentProcessors[i].ColorBuffer; |
|||
} |
|||
|
|||
for (int yy = this.PixelRowCounter; yy < maxY; yy++) |
|||
{ |
|||
int y = yy - this.PixelRowCounter; |
|||
|
|||
var values = new JpegColorConverter.ComponentValues(buffers, y); |
|||
this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); |
|||
|
|||
Span<TPixel> destRow = destination.GetPixelRowSpan(yy); |
|||
|
|||
// TODO: Investigate if slicing is actually necessary
|
|||
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Converter used to convert jpeg spectral data.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is tightly coupled with <see cref="HuffmanScanDecoder"/> and <see cref="JpegDecoderCore"/>.
|
|||
/// </remarks>
|
|||
internal abstract class SpectralConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Injects jpeg image decoding metadata.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is guaranteed to be called only once at SOF marker by <see cref="HuffmanScanDecoder"/>.
|
|||
/// </remarks>
|
|||
/// <param name="frame"><see cref="JpegFrame"/> instance containing decoder-specific parameters.</param>
|
|||
/// <param name="jpegData"><see cref="IRawJpegData"/> instance containing decoder-specific parameters.</param>
|
|||
public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); |
|||
|
|||
/// <summary>
|
|||
/// Called once per spectral stride for each component in <see cref="HuffmanScanDecoder"/>.
|
|||
/// This is called only for baseline interleaved jpegs.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Spectral 'stride' doesn't particularly mean 'single stride'.
|
|||
/// Actual stride height depends on the subsampling factor of the given component.
|
|||
/// </remarks>
|
|||
public abstract void ConvertStrideBaseline(); |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|||
{ |
|||
internal sealed class SpectralConverter<TPixel> : SpectralConverter, IDisposable |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly Configuration configuration; |
|||
|
|||
private CancellationToken cancellationToken; |
|||
|
|||
private JpegComponentPostProcessor[] componentProcessors; |
|||
|
|||
private JpegColorConverter colorConverter; |
|||
|
|||
private IMemoryOwner<Vector4> rgbaBuffer; |
|||
|
|||
private Buffer2D<TPixel> pixelBuffer; |
|||
|
|||
private int blockRowsPerStep; |
|||
|
|||
private int pixelRowsPerStep; |
|||
|
|||
private int pixelRowCounter; |
|||
|
|||
public SpectralConverter(Configuration configuration, CancellationToken cancellationToken) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.cancellationToken = cancellationToken; |
|||
} |
|||
|
|||
private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height; |
|||
|
|||
public Buffer2D<TPixel> PixelBuffer |
|||
{ |
|||
get |
|||
{ |
|||
if (!this.Converted) |
|||
{ |
|||
int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); |
|||
|
|||
for (int step = 0; step < steps; step++) |
|||
{ |
|||
this.cancellationToken.ThrowIfCancellationRequested(); |
|||
this.ConvertNextStride(step); |
|||
} |
|||
} |
|||
|
|||
return this.pixelBuffer; |
|||
} |
|||
} |
|||
|
|||
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) |
|||
{ |
|||
MemoryAllocator allocator = this.configuration.MemoryAllocator; |
|||
|
|||
// iteration data
|
|||
IJpegComponent c0 = frame.Components[0]; |
|||
|
|||
const int blockPixelHeight = 8; |
|||
this.blockRowsPerStep = c0.SamplingFactors.Height; |
|||
this.pixelRowsPerStep = this.blockRowsPerStep * blockPixelHeight; |
|||
|
|||
// pixel buffer for resulting image
|
|||
this.pixelBuffer = allocator.Allocate2D<TPixel>(frame.PixelWidth, frame.PixelHeight, AllocationOptions.Clean); |
|||
|
|||
// component processors from spectral to Rgba32
|
|||
var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep); |
|||
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; |
|||
for (int i = 0; i < this.componentProcessors.Length; i++) |
|||
{ |
|||
this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, jpegData, postProcessorBufferSize, frame.Components[i]); |
|||
} |
|||
|
|||
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
|
|||
this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth); |
|||
|
|||
// color converter from Rgba32 to TPixel
|
|||
this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); |
|||
} |
|||
|
|||
public override void ConvertStrideBaseline() |
|||
{ |
|||
// Convert next pixel stride using single spectral `stride'
|
|||
// Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor
|
|||
this.ConvertNextStride(spectralStep: 0); |
|||
|
|||
// Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride
|
|||
// Which leads to decoding artifacts
|
|||
// Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride
|
|||
foreach (JpegComponentPostProcessor cpp in this.componentProcessors) |
|||
{ |
|||
cpp.ClearSpectralBuffers(); |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (this.componentProcessors != null) |
|||
{ |
|||
foreach (JpegComponentPostProcessor cpp in this.componentProcessors) |
|||
{ |
|||
cpp.Dispose(); |
|||
} |
|||
} |
|||
|
|||
this.rgbaBuffer?.Dispose(); |
|||
} |
|||
|
|||
private void ConvertNextStride(int spectralStep) |
|||
{ |
|||
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); |
|||
|
|||
var buffers = new Buffer2D<float>[this.componentProcessors.Length]; |
|||
for (int i = 0; i < this.componentProcessors.Length; i++) |
|||
{ |
|||
this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); |
|||
buffers[i] = this.componentProcessors[i].ColorBuffer; |
|||
} |
|||
|
|||
for (int yy = this.pixelRowCounter; yy < maxY; yy++) |
|||
{ |
|||
int y = yy - this.pixelRowCounter; |
|||
|
|||
var values = new JpegColorConverter.ComponentValues(buffers, y); |
|||
this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan()); |
|||
|
|||
Span<TPixel> destRow = this.pixelBuffer.GetRowSpan(yy); |
|||
|
|||
// TODO: Investigate if slicing is actually necessary
|
|||
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow); |
|||
} |
|||
|
|||
this.pixelRowCounter += this.pixelRowsPerStep; |
|||
} |
|||
} |
|||
} |
|||
@ -1,97 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|||
{ |
|||
[Trait("Format", "Jpg")] |
|||
public class JpegImagePostProcessorTests |
|||
{ |
|||
public static string[] BaselineTestJpegs = |
|||
{ |
|||
TestImages.Jpeg.Baseline.Calliphora, |
|||
TestImages.Jpeg.Baseline.Cmyk, |
|||
TestImages.Jpeg.Baseline.Ycck, |
|||
TestImages.Jpeg.Baseline.Jpeg400, |
|||
TestImages.Jpeg.Baseline.Testorig420, |
|||
TestImages.Jpeg.Baseline.Jpeg444, |
|||
}; |
|||
|
|||
public JpegImagePostProcessorTests(ITestOutputHelper output) |
|||
{ |
|||
this.Output = output; |
|||
} |
|||
|
|||
private ITestOutputHelper Output { get; } |
|||
|
|||
private static void SaveBuffer<TPixel>(JpegComponentPostProcessor cp, TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
using (Image<Rgba32> image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) |
|||
{ |
|||
image.DebugSave(provider, $"-C{cp.Component.Index}-"); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] |
|||
[WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] |
|||
public void DoProcessorStep<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
string imageFile = provider.SourceFileOrDescription; |
|||
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) |
|||
using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) |
|||
using (var imageFrame = new ImageFrame<Rgba32>(Configuration.Default, decoder.ImageWidth, decoder.ImageHeight)) |
|||
{ |
|||
pp.DoPostProcessorStep(imageFrame); |
|||
|
|||
JpegComponentPostProcessor[] cp = pp.ComponentProcessors; |
|||
|
|||
SaveBuffer(cp[0], provider); |
|||
SaveBuffer(cp[1], provider); |
|||
SaveBuffer(cp[2], provider); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] |
|||
public void PostProcess<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
string imageFile = provider.SourceFileOrDescription; |
|||
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) |
|||
using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) |
|||
using (var image = new Image<Rgba32>(decoder.ImageWidth, decoder.ImageHeight)) |
|||
{ |
|||
pp.PostProcess(image.Frames.RootFrame, default); |
|||
|
|||
image.DebugSave(provider); |
|||
|
|||
ImagingTestCaseUtility testUtil = provider.Utility; |
|||
testUtil.TestGroupName = nameof(JpegDecoderTests); |
|||
testUtil.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; |
|||
|
|||
using (Image<TPixel> referenceImage = |
|||
provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false)) |
|||
{ |
|||
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); |
|||
|
|||
this.Output.WriteLine($"*** {imageFile} ***"); |
|||
this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); |
|||
|
|||
// ReSharper disable once PossibleInvalidOperationException
|
|||
Assert.True(report.TotalNormalizedDifference.Value < 0.005f); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|||
{ |
|||
[Trait("Format", "Jpg")] |
|||
public class SpectralToPixelConversionTests |
|||
{ |
|||
public static readonly string[] BaselineTestJpegs = |
|||
{ |
|||
TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, |
|||
TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, |
|||
TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, |
|||
TestImages.Jpeg.Baseline.MultiScanBaselineCMYK |
|||
}; |
|||
|
|||
public SpectralToPixelConversionTests(ITestOutputHelper output) |
|||
{ |
|||
this.Output = output; |
|||
} |
|||
|
|||
private ITestOutputHelper Output { get; } |
|||
|
|||
[Theory] |
|||
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] |
|||
public void Decoder_PixelBufferComparison<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
// Stream
|
|||
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; |
|||
using var ms = new MemoryStream(sourceBytes); |
|||
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); |
|||
|
|||
// Decoding
|
|||
using var converter = new SpectralConverter<TPixel>(Configuration.Default, cancellationToken: default); |
|||
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); |
|||
var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); |
|||
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); |
|||
|
|||
// Test metadata
|
|||
provider.Utility.TestGroupName = nameof(JpegDecoderTests); |
|||
provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; |
|||
|
|||
// Comparison
|
|||
using (Image<TPixel> image = new Image<TPixel>(Configuration.Default, converter.PixelBuffer, new ImageMetadata())) |
|||
using (Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false)) |
|||
{ |
|||
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); |
|||
|
|||
this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); |
|||
this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); |
|||
|
|||
// ReSharper disable once PossibleInvalidOperationException
|
|||
Assert.True(report.TotalNormalizedDifference.Value < 0.005f); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:a76832570111a868ea6cb6e8287aae1976c575c94c63880c74346a4b5db5d305 |
|||
size 27007 |
|||
oid sha256:2b5e1d91fb6dc1ddb696fbee63331ba9c6ef3548b619c005887e60c5b01f4981 |
|||
size 27303 |
|||
|
|||
Loading…
Reference in new issue