diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs
index 5134dda4e0..95ac123472 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs
@@ -228,22 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
///
/// List of component color processors.
/// Row to convert
- public ComponentValues(IReadOnlyList processors, int row)
- {
- DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));
-
- this.ComponentCount = processors.Count;
-
- this.Component0 = processors[0].GetColorBufferRowSpan(row);
-
- // In case of grayscale, Component1 and Component2 point to Component0 memory area
- this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0;
- this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0;
- this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty;
- }
-
- // TODO: experimental
- public ComponentValues(IReadOnlyList processors, int row)
+ public ComponentValues(IReadOnlyList processors, int row)
{
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs
new file mode 100644
index 0000000000..14109b796b
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ internal abstract class ComponentProcessor : IDisposable
+ {
+ public ComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, Size postProcessorBufferSize, IJpegComponent component, int blockSize)
+ {
+ this.Frame = frame;
+ this.Component = component;
+
+ this.BlockAreaSize = component.SubSamplingDivisors * blockSize;
+ this.ColorBuffer = memoryAllocator.Allocate2DOveraligned(
+ postProcessorBufferSize.Width,
+ postProcessorBufferSize.Height,
+ this.BlockAreaSize.Height);
+ }
+
+ protected JpegFrame Frame { get; }
+
+ protected IJpegComponent Component { get; }
+
+ protected Buffer2D ColorBuffer { get; }
+
+ protected Size BlockAreaSize { get; }
+
+ public abstract void CopyBlocksToColorBuffer(int spectralStep);
+
+ public void ClearSpectralBuffers()
+ {
+ Buffer2D spectralBlocks = this.Component.SpectralBlocks;
+ for (int i = 0; i < spectralBlocks.Height; i++)
+ {
+ spectralBlocks.DangerousGetRowSpan(i).Clear();
+ }
+ }
+
+ public Span GetColorBufferRowSpan(int row) =>
+ this.ColorBuffer.DangerousGetRowSpan(row);
+
+ public void Dispose() => this.ColorBuffer.Dispose();
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs
new file mode 100644
index 0000000000..8cb3e1ef57
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ internal sealed class DirectComponentProcessor : ComponentProcessor
+ {
+ private readonly IRawJpegData rawJpeg;
+
+ public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
+ : base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8)
+ => this.rawJpeg = rawJpeg;
+
+ public override void CopyBlocksToColorBuffer(int spectralStep)
+ {
+ Buffer2D spectralBuffer = this.Component.SpectralBlocks;
+
+ float maximumValue = this.Frame.MaxColorChannelValue;
+
+ int destAreaStride = this.ColorBuffer.Width;
+
+ int blocksRowsPerStep = this.Component.SamplingFactors.Height;
+
+ int yBlockStart = spectralStep * blocksRowsPerStep;
+
+ Size subSamplingDivisors = this.Component.SubSamplingDivisors;
+
+ Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.Component.QuantizationTableIndex];
+ Block8x8F workspaceBlock = default;
+
+ for (int y = 0; y < blocksRowsPerStep; y++)
+ {
+ int yBuffer = y * this.BlockAreaSize.Height;
+
+ Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
+ Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
+
+ for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
+ {
+ // Integer to float
+ workspaceBlock.LoadFrom(ref blockRow[xBlock]);
+
+ // Dequantize
+ workspaceBlock.MultiplyInPlace(ref dequantTable);
+
+ // Convert from spectral to color
+ FastFloatingPointDCT.TransformIDCT(ref workspaceBlock);
+
+ // To conform better to libjpeg we actually NEED TO loose precision here.
+ // This is because they store blocks as Int16 between all the operations.
+ // To be "more accurate", we need to emulate this by rounding!
+ workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
+
+ // Write to color buffer acording to sampling factors
+ int xColorBufferStart = xBlock * this.BlockAreaSize.Width;
+ workspaceBlock.ScaledCopyTo(
+ ref colorBufferRow[xColorBufferStart],
+ destAreaStride,
+ subSamplingDivisors.Width,
+ subSamplingDivisors.Height);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs
new file mode 100644
index 0000000000..0bcbf60b4a
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ internal sealed class DownScalingComponentProcessor8 : ComponentProcessor
+ {
+ private readonly float dcDequantizer;
+
+ public DownScalingComponentProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
+ : base(memoryAllocator, frame, postProcessorBufferSize, component, 1)
+ => this.dcDequantizer = rawJpeg.QuantizationTables[component.QuantizationTableIndex][0];
+
+ public override void CopyBlocksToColorBuffer(int spectralStep)
+ {
+ Buffer2D spectralBuffer = this.Component.SpectralBlocks;
+
+ float maximumValue = this.Frame.MaxColorChannelValue;
+
+ int blocksRowsPerStep = this.Component.SamplingFactors.Height;
+
+ int yBlockStart = spectralStep * blocksRowsPerStep;
+
+ for (int y = 0; y < blocksRowsPerStep; y++)
+ {
+ int yBuffer = y * this.BlockAreaSize.Height;
+
+ Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
+ Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
+
+ for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
+ {
+ // get Direct current term - averaged 8x8 pixel value
+ float dc = blockRow[xBlock][0];
+
+ // dequantization
+ dc *= this.dcDequantizer;
+
+ // Normalize & round
+ dc = (float)Math.Round(Numerics.Clamp(dc + MathF.Ceiling(maximumValue / 2), 0, maximumValue));
+
+ // Save to the intermediate buffer
+ int xColorBufferStart = xBlock * this.BlockAreaSize.Width;
+ colorBufferRow[xColorBufferStart] = dc;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor.cs
deleted file mode 100644
index 1236117267..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
-{
- ///
- /// Encapsulates spectral data to rgba32 processing for one component.
- ///
- internal class JpegComponentPostProcessor : IDisposable
- {
- ///
- /// The size of the area in corresponding to one 8x8 Jpeg block
- ///
- private readonly Size blockAreaSize;
-
- ///
- /// Jpeg frame instance containing required decoding metadata.
- ///
- private readonly JpegFrame frame;
-
- ///
- /// Gets the component containing decoding meta information.
- ///
- private readonly IJpegComponent component;
-
- ///
- /// Gets the instance containing decoding meta information.
- ///
- private readonly IRawJpegData rawJpeg;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
- {
- this.frame = frame;
-
- this.component = component;
- this.rawJpeg = rawJpeg;
- this.blockAreaSize = this.component.SubSamplingDivisors * 8;
- this.ColorBuffer = memoryAllocator.Allocate2DOveraligned(
- postProcessorBufferSize.Width,
- postProcessorBufferSize.Height,
- this.blockAreaSize.Height);
- }
-
- ///
- /// Gets the temporary working buffer of color values.
- ///
- public Buffer2D ColorBuffer { get; }
-
- ///
- public void Dispose() => this.ColorBuffer.Dispose();
-
- ///
- /// Convert raw spectral DCT data to color data and copy it to the color buffer .
- ///
- public void CopyBlocksToColorBuffer(int spectralStep)
- {
- Buffer2D spectralBuffer = this.component.SpectralBlocks;
-
- float maximumValue = this.frame.MaxColorChannelValue;
-
- int destAreaStride = this.ColorBuffer.Width;
-
- int blocksRowsPerStep = this.component.SamplingFactors.Height;
-
- int yBlockStart = spectralStep * blocksRowsPerStep;
-
- Size subSamplingDivisors = this.component.SubSamplingDivisors;
-
- Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex];
- Block8x8F workspaceBlock = default;
-
- for (int y = 0; y < blocksRowsPerStep; y++)
- {
- int yBuffer = y * this.blockAreaSize.Height;
-
- Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
- Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
-
- for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
- {
- // Integer to float
- workspaceBlock.LoadFrom(ref blockRow[xBlock]);
-
- // Dequantize
- workspaceBlock.MultiplyInPlace(ref dequantTable);
-
- // Convert from spectral to color
- FastFloatingPointDCT.TransformIDCT(ref workspaceBlock);
-
- // To conform better to libjpeg we actually NEED TO loose precision here.
- // This is because they store blocks as Int16 between all the operations.
- // To be "more accurate", we need to emulate this by rounding!
- workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
-
- // Write to color buffer acording to sampling factors
- int xColorBufferStart = xBlock * this.blockAreaSize.Width;
- workspaceBlock.ScaledCopyTo(
- ref colorBufferRow[xColorBufferStart],
- destAreaStride,
- subSamplingDivisors.Width,
- subSamplingDivisors.Height);
- }
- }
- }
-
- public void ClearSpectralBuffers()
- {
- Buffer2D spectralBlocks = this.component.SpectralBlocks;
- for (int i = 0; i < spectralBlocks.Height; i++)
- {
- spectralBlocks.DangerousGetRowSpan(i).Clear();
- }
- }
-
- public Span GetColorBufferRowSpan(int row) =>
- this.ColorBuffer.DangerousGetRowSpan(row);
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor8.cs
deleted file mode 100644
index cb082f6ccb..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor8.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
-{
- ///
- /// Encapsulates spectral data to rgba32 processing for one component.
- ///
- internal class JpegComponentPostProcessor8 : IDisposable
- {
- ///
- /// The size of the area in corresponding to one 8x8 Jpeg block
- ///
- private readonly Size blockAreaSize;
-
- ///
- /// Jpeg frame instance containing required decoding metadata.
- ///
- private readonly JpegFrame frame;
-
- ///
- /// Gets the component containing decoding meta information.
- ///
- private readonly IJpegComponent component;
-
- ///
- /// Gets the instance containing decoding meta information.
- ///
- private readonly IRawJpegData rawJpeg;
-
- private readonly float dcDequantizer;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public JpegComponentPostProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
- {
- // TODO: this must be a variable depending on dct scale factor
- const int blockSize = 1;
-
- this.frame = frame;
-
- this.component = component;
-
- this.rawJpeg = rawJpeg;
-
- this.dcDequantizer = rawJpeg.QuantizationTables[this.component.QuantizationTableIndex][0];
-
- this.blockAreaSize = this.component.SubSamplingDivisors * blockSize;
- this.ColorBuffer = memoryAllocator.Allocate2DOveraligned(
- postProcessorBufferSize.Width,
- postProcessorBufferSize.Height,
- this.blockAreaSize.Height);
- }
-
- ///
- /// Gets the temporary working buffer of color values.
- ///
- public Buffer2D ColorBuffer { get; }
-
- ///
- public void Dispose() => this.ColorBuffer.Dispose();
-
- ///
- /// Convert raw spectral DCT data to color data and copy it to the color buffer .
- ///
- public void CopyBlocksToColorBuffer(int spectralStep)
- {
- Buffer2D spectralBuffer = this.component.SpectralBlocks;
-
- float maximumValue = this.frame.MaxColorChannelValue;
-
- int blocksRowsPerStep = this.component.SamplingFactors.Height;
-
- int yBlockStart = spectralStep * blocksRowsPerStep;
-
- for (int y = 0; y < blocksRowsPerStep; y++)
- {
- int yBuffer = y * this.blockAreaSize.Height;
-
- Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
- Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
-
- for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
- {
- // get DC - averaged 8x8 pixel value
- float DC = blockRow[xBlock][0];
-
- // dequantization
- DC *= this.dcDequantizer;
-
- // Normalize & round
- DC = (float)Math.Round(Numerics.Clamp(DC + MathF.Ceiling(maximumValue / 2), 0, maximumValue));
-
- // Save to the intermediate buffer
- int xColorBufferStart = xBlock * this.blockAreaSize.Width;
- colorBufferRow[xColorBufferStart] = DC;
-
- //// Integer to float
- //workspaceBlock.LoadFrom(ref blockRow[xBlock]);
-
- //// Dequantize
- //workspaceBlock.MultiplyInPlace(ref dequantTable);
-
- //// Convert from spectral to color
- //FastFloatingPointDCT.TransformIDCT(ref workspaceBlock);
-
- //// To conform better to libjpeg we actually NEED TO loose precision here.
- //// This is because they store blocks as Int16 between all the operations.
- //// To be "more accurate", we need to emulate this by rounding!
- //workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
-
- //// Write to color buffer acording to sampling factors
- //int xColorBufferStart = xBlock * this.blockAreaSize.Width;
- //workspaceBlock.ScaledCopyTo(
- // ref colorBufferRow[xColorBufferStart],
- // destAreaStride,
- // subSamplingDivisors.Width,
- // subSamplingDivisors.Height);
- }
- }
- }
-
- public void ClearSpectralBuffers()
- {
- Buffer2D spectralBlocks = this.component.SpectralBlocks;
- for (int i = 0; i < spectralBlocks.Height; i++)
- {
- spectralBlocks.DangerousGetRowSpan(i).Clear();
- }
- }
-
- public Span GetColorBufferRowSpan(int row) =>
- this.ColorBuffer.DangerousGetRowSpan(row);
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/ResizingSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/ResizingSpectralConverter{TPixel}.cs
deleted file mode 100644
index 65cc6ea244..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/ResizingSpectralConverter{TPixel}.cs
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Buffers;
-using System.Linq;
-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
-{
- // TODO: docs
- internal class ResizingSpectralConverter : SpectralConverter, IDisposable
- where TPixel : unmanaged, IPixel
- {
- ///
- /// instance associated with current
- /// decoding routine.
- ///
- private readonly Configuration configuration;
-
- ///
- /// Target image size after scaled decoding.
- ///
- private readonly Size targetSize;
-
- ///
- /// Jpeg component converters from decompressed spectral to color data.
- ///
- private JpegComponentPostProcessor8[] componentProcessors;
-
- ///
- /// Resulting 2D pixel buffer.
- ///
- private Buffer2D pixelBuffer;
-
- ///
- /// How many pixel rows are processed in one 'stride'.
- ///
- private int pixelRowsPerStep;
-
- ///
- /// How many pixel rows were processed.
- ///
- private int pixelRowCounter;
-
- ///
- /// Intermediate buffer of RGB components used in color conversion.
- ///
- private IMemoryOwner rgbBuffer;
-
- ///
- /// Proxy buffer used in packing from RGB to target TPixel pixels.
- ///
- private IMemoryOwner paddedProxyPixelRow;
-
- ///
- /// Color converter from jpeg color space to target pixel color space.
- ///
- private JpegColorConverterBase colorConverter;
-
- public ResizingSpectralConverter(Configuration configuration, Size targetSize)
- {
- this.configuration = configuration;
- this.targetSize = targetSize;
- }
-
- public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
- {
- MemoryAllocator allocator = this.configuration.MemoryAllocator;
-
- (int width, int height, int scaleDenominator) = GetScaledImageDimensions(frame.PixelWidth, frame.PixelHeight, this.targetSize.Width, this.targetSize.Height);
-
- // iteration data
- int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width);
- int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height);
-
- this.pixelBuffer = allocator.Allocate2D(
- width,
- height,
- this.configuration.PreferContiguousImageBuffers);
-
- this.paddedProxyPixelRow = allocator.Allocate(width + 3);
-
- // single 'stride' rgba32 buffer for conversion between spectral and TPixel
- this.rgbBuffer = allocator.Allocate(width * 3);
-
- // component processors from spectral to Rgba32
- int blockPixelSize = 8 / scaleDenominator;
- this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize;
-
- // color converter
- JpegColorConverterBase converter = this.GetColorConverter(frame, jpegData);
- this.colorConverter = converter;
-
- int bufferWidth = majorBlockWidth * blockPixelSize;
- int batchSize = converter.ElementsPerBatch;
- int correctedBufferWidth = bufferWidth + (batchSize - (bufferWidth % batchSize));
- var postProcessorBufferSize = new Size(correctedBufferWidth, this.pixelRowsPerStep);
-
- this.componentProcessors = new JpegComponentPostProcessor8[frame.Components.Length];
- for (int i = 0; i < this.componentProcessors.Length; i++)
- {
- this.componentProcessors[i] = new JpegComponentPostProcessor8(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
- }
- }
-
- 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.ConvertStride(spectralStep: 0);
-
- foreach (JpegComponentPostProcessor8 cpp in this.componentProcessors)
- {
- cpp.ClearSpectralBuffers();
- }
- }
-
- ///
- /// Converts single spectral jpeg stride to color stride.
- ///
- /// Spectral stride index.
- private void ConvertStride(int spectralStep)
- {
- int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep);
-
- for (int i = 0; i < this.componentProcessors.Length; i++)
- {
- this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep);
- }
-
- int width = this.pixelBuffer.Width;
-
- for (int yy = this.pixelRowCounter; yy < maxY; yy++)
- {
- int y = yy - this.pixelRowCounter;
-
- var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y);
-
- this.colorConverter.ConvertToRgbInplace(values);
- values = values.Slice(0, width); // slice away Jpeg padding
-
- Span r = this.rgbBuffer.Slice(0, width);
- Span g = this.rgbBuffer.Slice(width, width);
- Span b = this.rgbBuffer.Slice(width * 2, width);
-
- SimdUtils.NormalizedFloatToByteSaturate(values.Component0.Slice(0, width), r);
- SimdUtils.NormalizedFloatToByteSaturate(values.Component1.Slice(0, width), g);
- SimdUtils.NormalizedFloatToByteSaturate(values.Component2.Slice(0, width), b);
-
- // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row.
- // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row,
- // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row.
- if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow))
- {
- PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow);
- }
- else
- {
- Span proxyRow = this.paddedProxyPixelRow.GetSpan();
- PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow);
- proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy));
- }
- }
-
- this.pixelRowCounter += this.pixelRowsPerStep;
- }
-
- public override Buffer2D GetPixelBuffer(CancellationToken cancellationToken)
- {
- if (!this.Converted)
- {
- int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep);
-
- for (int step = 0; step < steps; step++)
- {
- cancellationToken.ThrowIfCancellationRequested();
- this.ConvertStride(step);
- }
- }
-
- return this.pixelBuffer;
- }
-
- public void Dispose()
- {
- if (this.componentProcessors != null)
- {
- foreach (JpegComponentPostProcessor8 cpp in this.componentProcessors)
- {
- cpp.Dispose();
- }
- }
-
- this.rgbBuffer?.Dispose();
- this.paddedProxyPixelRow?.Dispose();
- }
-
- // TODO: docs, code formatting
- private static readonly (int Num, int Denom)[] ScalingFactors = new (int, int)[]
- {
- /* upscaling factors */
- // (16, 8),
- // (15, 8),
- // (14, 8),
- // (13, 8),
- // (12, 8),
- // (11, 8),
- // (10, 8),
- // (9, 8),
-
- /* no scaling */
- (8, 8),
-
- /* downscaling factors */
- // (7, 8), // 8 => 7
- // (6, 8), // 8 => 6
- // (5, 8), // 8 => 5
- // (4, 8), // 1/2 dct scaling - currently not supported
- // (3, 8), // 8 => 3
- // (2, 8), // 1/4 dct scaling - currently not supported
- (1, 8), // 1/8 dct scaling
- };
-
- ///
- /// TODO: docs, code formatting
- ///
- /// Initial image width.
- /// Initial image height.
- /// Target image width.
- /// Target image height.
- private static (int Width, int Height, int ScaleDenominator) GetScaledImageDimensions(int iWidth, int iHeight, int tWidth, int tHeight)
- {
- int output_width = iWidth;
- int output_height = iHeight;
- int dct_scale = 8;
-
- for (int i = 1; i < ScalingFactors.Length; i++)
- {
- (int num, int denom) = ScalingFactors[i];
- int scaledw = (int)Numerics.DivideCeil((uint)(iWidth * num), (uint)denom);
- int scaledh = (int)Numerics.DivideCeil((uint)(iHeight * num), (uint)denom);
-
- if (scaledw < tWidth || scaledh < tHeight)
- {
- dct_scale = 8 / ScalingFactors[i - 1].Num;
- break;
- }
-
- output_width = scaledw;
- output_height = scaledh;
- }
-
- return (output_width, output_height, dct_scale);
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter{TPixel}.cs
deleted file mode 100644
index c0ed4c9d84..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter{TPixel}.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System.Threading;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
-{
- // TODO: docs
- internal abstract class SpectralConverter : SpectralConverter
- where TPixel : unmanaged, IPixel
- {
- ///
- /// Gets converted pixel buffer.
- ///
- /// Cancellation token.
- /// Pixel buffer.
- public abstract Buffer2D GetPixelBuffer(CancellationToken cancellationToken);
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
similarity index 100%
rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter.cs
rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/DirectSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
similarity index 58%
rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/DirectSpectralConverter{TPixel}.cs
rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
index 21ce7a16d3..23ce946678 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/DirectSpectralConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
@@ -22,9 +22,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
///
///
- internal class DirectSpectralConverter : SpectralConverter, IDisposable
+ internal class SpectralConverter : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel
{
+ ///
+ /// Supported scaling factors for DCT jpeg scaling.
+ ///
+ private static readonly int[] ScalingFactors = new int[]
+ {
+ // 8 => 8, no scaling
+ 8,
+
+ // 8 => 1, 1/8 of the original size
+ 1,
+ };
+
///
/// instance associated with current
/// decoding routine.
@@ -34,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Jpeg component converters from decompressed spectral to color data.
///
- private JpegComponentPostProcessor[] componentProcessors;
+ private ComponentProcessor[] componentProcessors;
///
/// Color converter from jpeg color space to target pixel color space.
@@ -67,18 +79,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private int pixelRowCounter;
///
- /// Initializes a new instance of the class.
+ /// Represent target size after decoding for scaling decoding mode.
+ ///
+ ///
+ /// Null if no scaling is required.
+ ///
+ private Size? targetSize;
+
+ ///
+ /// Initializes a new instance of the class.
///
/// The configuration.
- public DirectSpectralConverter(Configuration configuration) =>
+ /// Optional target size for decoded image.
+ public SpectralConverter(Configuration configuration, Size? targetSize = null)
+ {
this.configuration = configuration;
- ///
+ this.targetSize = targetSize;
+ }
+
+ ///
+ /// Gets converted pixel buffer.
+ ///
///
/// For non-baseline interleaved jpeg this method does a 'lazy' spectral
/// conversion from spectral to color.
///
- public override Buffer2D GetPixelBuffer(CancellationToken cancellationToken)
+ /// Cancellation token.
+ /// Pixel buffer.
+ public Buffer2D GetPixelBuffer(CancellationToken cancellationToken)
{
if (!this.Converted)
{
@@ -94,52 +123,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return this.pixelBuffer;
}
- ///
- public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
+ ///
+ /// Calculates resulting image size and jpeg block scaling.
+ ///
+ /// Native size of the image.
+ /// Resulting jpeg block pixel size.
+ /// Scaled jpeg image size.
+ private Size GetResultingImageSize(Size nativeSize, out int blockPixelSize)
{
- MemoryAllocator allocator = this.configuration.MemoryAllocator;
-
- // iteration data
- int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width);
- int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height);
-
- const int blockPixelHeight = 8;
- this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight;
-
- // pixel buffer for resulting image
- this.pixelBuffer = allocator.Allocate2D(
- frame.PixelWidth,
- frame.PixelHeight,
- this.configuration.PreferContiguousImageBuffers);
- this.paddedProxyPixelRow = allocator.Allocate(frame.PixelWidth + 3);
-
- // component processors from spectral to Rgba32
- const int blockPixelWidth = 8;
- var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep);
- this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length];
- for (int i = 0; i < this.componentProcessors.Length; i++)
+ if (this.targetSize == null)
{
- this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
+ blockPixelSize = 8;
+ return nativeSize;
}
+ else
+ {
+ const uint jpegBlockPixelSize = 8;
- // single 'stride' rgba32 buffer for conversion between spectral and TPixel
- this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3);
+ Size targetSize = this.targetSize.Value;
+ int outputWidth = nativeSize.Width;
+ int outputHeight = nativeSize.Height;
+ blockPixelSize = 1;
- // color converter from Rgba32 to TPixel
- this.colorConverter = this.GetColorConverter(frame, jpegData);
- }
-
- ///
- 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.ConvertStride(spectralStep: 0);
+ for (int i = 1; i < ScalingFactors.Length; i++)
+ {
+ int scale = ScalingFactors[i];
+ int scaledw = (int)Numerics.DivideCeil((uint)(nativeSize.Width * scale), jpegBlockPixelSize);
+ int scaledh = (int)Numerics.DivideCeil((uint)(nativeSize.Height * scale), jpegBlockPixelSize);
+
+ if (scaledw < targetSize.Width || scaledh < targetSize.Height)
+ {
+ blockPixelSize = ScalingFactors[i - 1];
+ break;
+ }
+
+ outputWidth = scaledw;
+ outputHeight = scaledh;
+ }
- foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
- {
- cpp.ClearSpectralBuffers();
+ return new Size(outputWidth, outputHeight);
}
}
@@ -193,12 +215,80 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.pixelRowCounter += this.pixelRowsPerStep;
}
+ ///
+ public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
+ {
+ MemoryAllocator allocator = this.configuration.MemoryAllocator;
+
+ // color converter from RGB to TPixel
+ JpegColorConverterBase converter = this.GetColorConverter(frame, jpegData);
+ this.colorConverter = converter;
+
+ // Resulting image size
+ Size pixelSize = this.GetResultingImageSize(frame.PixelSize, out int blockPixelSize);
+
+ // iteration data
+ int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width);
+ int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height);
+
+ this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize;
+
+ // pixel buffer for resulting image
+ this.pixelBuffer = allocator.Allocate2D(
+ pixelSize.Width,
+ pixelSize.Height,
+ this.configuration.PreferContiguousImageBuffers);
+ this.paddedProxyPixelRow = allocator.Allocate(pixelSize.Width + 3);
+
+ // component processors from spectral to RGB
+ int bufferWidth = majorBlockWidth * blockPixelSize;
+ int batchSize = converter.ElementsPerBatch;
+ int converterAlignedBufferWidth = bufferWidth + (batchSize - (bufferWidth % batchSize));
+ var postProcessorBufferSize = new Size(converterAlignedBufferWidth, this.pixelRowsPerStep);
+ this.componentProcessors = new ComponentProcessor[frame.Components.Length];
+ switch (blockPixelSize)
+ {
+ case 8:
+ for (int i = 0; i < this.componentProcessors.Length; i++)
+ {
+ this.componentProcessors[i] = new DirectComponentProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
+ }
+
+ break;
+ case 1:
+ for (int i = 0; i < this.componentProcessors.Length; i++)
+ {
+ this.componentProcessors[i] = new DownScalingComponentProcessor8(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
+ }
+
+ break;
+
+ // TODO: default?
+ }
+
+ // single 'stride' rgba32 buffer for conversion between spectral and TPixel
+ this.rgbBuffer = allocator.Allocate(pixelSize.Width * 3);
+ }
+
+ ///
+ public override void ConvertStrideBaseline()
+ {
+ // Convert next pixel stride using single spectral `stride'
+ // Note that zero passing eliminates extra virtual call
+ this.ConvertStride(spectralStep: 0);
+
+ foreach (ComponentProcessor cpp in this.componentProcessors)
+ {
+ cpp.ClearSpectralBuffers();
+ }
+ }
+
///
public void Dispose()
{
if (this.componentProcessors != null)
{
- foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
+ foreach (ComponentProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
index 295569c980..bab75d15c7 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
@@ -32,8 +32,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> this.Decode(configuration, stream, cancellationToken);
- // TODO: this implementation is experimental
- public Image experimental__DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
+ ///
+ /// TODO: this implementation is experimental
+ ///
+ /// Placeholder1
+ /// Placeholder2
+ /// Placeholder3
+ /// Placeholder4
+ /// Placeholder5
+ /// Placeholder6
+ public Image Experimental__DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
@@ -45,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
- return decoder.experimental__DecodeInto(bufferedReadStream, targetSize, cancellationToken);
+ return decoder.Experimental__DecodeInto(bufferedReadStream, targetSize, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 7709a58be6..269c1f9935 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -182,6 +182,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return new JpegFileMarker(marker[1], stream.Position - 2, true);
}
+ ///
+ public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ using var spectralConverter = new SpectralConverter(this.Configuration);
+ return this.Decode(stream, spectralConverter, cancellationToken);
+ }
+
+ // TODO: docs
+ public Image Experimental__DecodeInto(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ using var spectralConverter = new SpectralConverter(this.Configuration, targetSize);
+ return this.Decode(stream, spectralConverter, cancellationToken);
+ }
+
// TODO: docs
private Image Decode(BufferedReadStream stream, SpectralConverter converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
@@ -201,22 +217,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.Metadata);
}
- // TODO: docs
- public Image experimental__DecodeInto(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- using var converter = new ResizingSpectralConverter(this.Configuration, targetSize);
- return this.Decode(stream, converter, cancellationToken);
- }
-
- ///
- public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- using var converter = new DirectSpectralConverter(this.Configuration);
- return this.Decode(stream, converter, cancellationToken);
- }
-
///
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs
index e084fdf16d..5b793c35de 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// Spectral converter for gray TIFF's which use the JPEG compression.
///
/// The type of the pixel.
- internal sealed class GrayJpegSpectralConverter : DirectSpectralConverter
+ internal sealed class GrayJpegSpectralConverter : SpectralConverter
where TPixel : unmanaged, IPixel
{
///
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
index 87c34a8402..cfbc32f4f6 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
@@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
{
- using DirectSpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(this.configuration);
+ using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(this.configuration);
var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray);
jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None);
@@ -74,8 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
case TiffPhotometricInterpretation.YCbCr:
case TiffPhotometricInterpretation.Rgb:
{
- using DirectSpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ?
- new RgbJpegSpectralConverter(this.configuration) : new DirectSpectralConverter(this.configuration);
+ using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ?
+ new RgbJpegSpectralConverter(this.configuration) : new SpectralConverter(this.configuration);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
index 3e06152462..a83518064d 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// The jpeg data should be always treated as RGB color space.
///
/// The type of the pixel.
- internal sealed class RgbJpegSpectralConverter : DirectSpectralConverter
+ internal sealed class RgbJpegSpectralConverter : SpectralConverter
where TPixel : unmanaged, IPixel
{
///
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
index d64eb15ae4..0b2885d654 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
@@ -79,4 +79,21 @@ Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
| 'Baseline 4:2:0 Interleaved' | 8.458 ms | 0.0289 ms | 0.0256 ms |
| 'Baseline 4:0:0 (grayscale)' | 1.550 ms | 0.0050 ms | 0.0044 ms |
| 'Progressive 4:2:0 Non-Interleaved' | 13.220 ms | 0.0449 ms | 0.0398 ms |
+
+
+FRESH BENCHMARKS FOR NEW SPECTRAL CONVERSION SETUP
+
+BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044
+Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
+.NET SDK=6.0.100-preview.3.21202.5
+ [Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
+ DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT
+
+
+| Method | Mean | Error | StdDev |
+|------------------------------------ |----------:|----------:|----------:|
+| 'Baseline 4:4:4 Interleaved' | 10.734 ms | 0.0287 ms | 0.0254 ms |
+| 'Baseline 4:2:0 Interleaved' | 8.517 ms | 0.0401 ms | 0.0356 ms |
+| 'Baseline 4:0:0 (grayscale)' | 1.442 ms | 0.0051 ms | 0.0045 ms |
+| 'Progressive 4:2:0 Non-Interleaved' | 12.740 ms | 0.0832 ms | 0.0730 ms |
*/
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
index 322c044bb6..27240831c3 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
@@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
// Decoding
- using var converter = new DirectSpectralConverter(Configuration.Default);
+ using var converter = new SpectralConverter(Configuration.Default);
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default);
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);