diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
index acd98bcfcd..9b1eaaf470 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
@@ -10,6 +10,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
internal abstract class SpectralConverter
{
+ ///
+ /// Supported scaled spectral block sizes for scaled IDCT decoding.
+ ///
+ private static readonly int[] ScaledBlockSizes = new int[]
+ {
+ // 8 => 1, 1/8 of the original size
+ 1,
+
+ // 8 => 2, 1/4 of the original size
+ 2,
+
+ // 8 => 4, 1/2 of the original size
+ 4,
+ };
+
///
/// Gets a value indicating whether this converter has converted spectral
/// data of the current image or not.
@@ -58,5 +73,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// The raw JPEG data.
/// The color converter.
protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision);
+
+ ///
+ /// Calculates image size with optional scaling.
+ ///
+ ///
+ /// Does not apply scalling if is null.
+ ///
+ /// Size of the image.
+ /// Target size of the image.
+ /// Spectral block size, equals to 8 if scaling is not applied.
+ /// Resulting image size, equals to if scaling is not applied.
+ public static Size CalculateResultingImageSize(Size size, Size? targetSize, out int blockPixelSize)
+ {
+ const int blockNativePixelSize = 8;
+
+ blockPixelSize = blockNativePixelSize;
+ if (targetSize != null)
+ {
+ Size tSize = targetSize.Value;
+
+ int fullBlocksWidth = (int)((uint)size.Width / blockNativePixelSize);
+ int fullBlocksHeight = (int)((uint)size.Height / blockNativePixelSize);
+
+ int blockWidthRemainder = size.Width & (blockNativePixelSize - 1);
+ int blockHeightRemainder = size.Height & (blockNativePixelSize - 1);
+
+ for (int i = 0; i < ScaledBlockSizes.Length; i++)
+ {
+ int blockSize = ScaledBlockSizes[i];
+ int scaledWidth = (fullBlocksWidth * blockSize) + (int)Numerics.DivideCeil((uint)(blockWidthRemainder * blockSize), blockNativePixelSize);
+ int scaledHeight = (fullBlocksHeight * blockSize) + (int)Numerics.DivideCeil((uint)(blockHeightRemainder * blockSize), blockNativePixelSize);
+
+ if (scaledWidth >= tSize.Width && scaledHeight >= tSize.Height)
+ {
+ blockPixelSize = blockSize;
+ return new Size(scaledWidth, scaledHeight);
+ }
+ }
+ }
+
+ return size;
+ }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
index 3e09e7c997..820c03d126 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
@@ -25,24 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
internal class SpectralConverter : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel
{
- ///
- /// Supported scaling factors for DCT jpeg scaling.
- ///
- private static readonly int[] ScaledBlockSizes = new int[]
- {
- // 8 => 1, 1/8 of the original size
- 1,
-
- // 8 => 2, 1/4 of the original size
- 2,
-
- // 8 => 4, 1/2 of the original size
- 4,
-
- // 8 => 8, no scaling
- 8,
- };
-
///
/// instance associated with current
/// decoding routine.
@@ -126,81 +108,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
- var buffer = this.pixelBuffer;
+ Buffer2D buffer = this.pixelBuffer;
this.pixelBuffer = null;
return buffer;
}
- ///
- /// Calculates resulting image size.
- ///
- ///
- /// If is null, unchanged is returned.
- ///
- /// Size of the image.
- /// Target image size, can be null.
- /// Scaled spectral block size if IDCT scaling should be applied
- /// Scaled jpeg image size.
- // TODO: describe ALL outcomes of the built-in IDCT downscaling
- public static Size GetResultingImageSize(Size size, Size? targetSize, out int outputBlockSize)
- {
- const int jpegBlockPixelSize = 8;
-
- // must be at least 5% smaller than current IDCT scaled size
- // to perform second pass resizing
- // this is a highly experimental value
- const float secondPassThresholdRatio = 0.95f;
-
- outputBlockSize = jpegBlockPixelSize;
- if (targetSize != null)
- {
- Size tSize = targetSize.Value;
-
- int widthInBlocks = (int)Numerics.DivideCeil((uint)size.Width, jpegBlockPixelSize);
- int heightInBlocks = (int)Numerics.DivideCeil((uint)size.Height, jpegBlockPixelSize);
-
- for (int i = 0; i < ScaledBlockSizes.Length; i++)
- {
- int blockSize = ScaledBlockSizes[i];
- int scaledWidth = widthInBlocks * blockSize;
- int scaledHeight = heightInBlocks * blockSize;
-
- // skip to next IDCT scaling
- if (scaledWidth < tSize.Width || scaledHeight < tSize.Height)
- {
- // this if segment can be safely removed
- continue;
- }
-
- // exact match
- if (scaledWidth == tSize.Width && scaledHeight == tSize.Height)
- {
- outputBlockSize = blockSize;
- return new Size(scaledWidth, scaledHeight);
- }
-
- // center cropping
- int widthDiff = Math.Abs(tSize.Width - scaledWidth);
- int heightDiff = Math.Abs(tSize.Height - scaledHeight);
- if (widthDiff < blockSize && heightDiff < blockSize)
- {
- throw new NotSupportedException($"Central cropping is not supported yet");
- }
-
- // small enough for second pass
- float secondPassWidthRatio = (float)tSize.Width / scaledWidth;
- float secondPassHeightRatio = (float)tSize.Height / scaledHeight;
- if (secondPassWidthRatio <= secondPassThresholdRatio && secondPassHeightRatio <= secondPassThresholdRatio)
- {
- outputBlockSize = blockSize;
- return new Size(scaledWidth, scaledHeight);
- }
- }
- }
-
- return size;
- }
-
///
/// Converts single spectral jpeg stride to color stride.
///
@@ -260,8 +172,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
JpegColorConverterBase converter = this.GetColorConverter(frame, jpegData);
this.colorConverter = converter;
- // Resulting image size
- Size pixelSize = GetResultingImageSize(frame.PixelSize, this.targetSize, out int blockPixelSize);
+ // resulting image size
+ Size pixelSize = CalculateResultingImageSize(frame.PixelSize, this.targetSize, out int blockPixelSize);
// iteration data
int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width);
@@ -277,46 +189,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.paddedProxyPixelRow = allocator.Allocate(pixelSize.Width + 3);
// component processors from spectral to RGB
- // TODO: refactor this mess
int bufferWidth = majorBlockWidth * blockPixelSize;
int batchSize = converter.ElementsPerBatch;
- int batchRemainder = bufferWidth % batchSize;
- int converterAlignedBufferWidth = bufferWidth + (batchRemainder != 0 ? batchSize - batchRemainder : 0);
- 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 4:
- for (int i = 0; i < this.componentProcessors.Length; i++)
- {
- this.componentProcessors[i] = new DownScalingComponentProcessor2(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
- }
-
- break;
- case 2:
- for (int i = 0; i < this.componentProcessors.Length; i++)
- {
- this.componentProcessors[i] = new DownScalingComponentProcessor4(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;
- default:
- throw new Exception("This is a debug message which should NEVER be seen in release build");
- }
+ int batchRemainder = bufferWidth & (batchSize - 1);
+ var postProcessorBufferSize = new Size(bufferWidth + (batchSize - batchRemainder), this.pixelRowsPerStep);
+ this.componentProcessors = this.CreateComponentProcessors(frame, jpegData, blockPixelSize, postProcessorBufferSize);
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
this.rgbBuffer = allocator.Allocate(pixelSize.Width * 3);
@@ -335,6 +212,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
+ protected ComponentProcessor[] CreateComponentProcessors(JpegFrame frame, IRawJpegData jpegData, int blockPixelSize, Size processorBufferSize)
+ {
+ MemoryAllocator allocator = this.configuration.MemoryAllocator;
+ var componentProcessors = new ComponentProcessor[frame.Components.Length];
+ for (int i = 0; i < componentProcessors.Length; i++)
+ {
+ componentProcessors[i] = blockPixelSize switch
+ {
+ 4 => new DownScalingComponentProcessor2(allocator, frame, jpegData, processorBufferSize, frame.Components[i]),
+ 2 => new DownScalingComponentProcessor4(allocator, frame, jpegData, processorBufferSize, frame.Components[i]),
+ 1 => new DownScalingComponentProcessor8(allocator, frame, jpegData, processorBufferSize, frame.Components[i]),
+ _ => new DirectComponentProcessor(allocator, frame, jpegData, processorBufferSize, frame.Components[i]),
+ };
+ }
+
+ return componentProcessors;
+ }
+
///
public void Dispose()
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
index 60ada0e0e0..07808820aa 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
@@ -6,7 +6,6 @@ using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.Jpeg
{
@@ -40,35 +39,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Placeholder4
/// Placeholder5
/// Placeholder6
- public Image Experimental__DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
- => this.Experimental__DecodeInto(configuration, stream, targetSize, cancellationToken);
+ internal Image DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
+ => this.DecodeInto(configuration, stream, targetSize, cancellationToken);
///
- /// Placeholder summary.
+ /// Decodes and downscales the image from the specified stream if possible.
///
- /// Placeholder1
- /// Placeholder2
- /// Placeholder3
- /// Placeholder4
- /// Placeholder5
- /// Placeholder6
- public Image Experimental__DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
+ /// The pixel format.
+ /// Configuration.
+ /// Stream.
+ /// Target size.
+ /// Cancellation token.
+ internal Image DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
-
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
- Image img = decoder.Experimental__DecodeInto(bufferedReadStream, targetSize, cancellationToken);
- if (img.Size() != targetSize)
- {
- img.Mutate(ctx => ctx.Resize(targetSize, KnownResamplers.Box, compand: false));
- }
-
- return img;
+ return decoder.DecodeInto(bufferedReadStream, targetSize, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index daacd862b9..aa83eced88 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -185,24 +185,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
- {
- using var spectralConverter = new SpectralConverter(this.Configuration);
- return this.Decode(stream, spectralConverter, cancellationToken);
- }
+ => this.Decode(stream, targetSize: null, cancellationToken);
- // TODO: docs
- public Image Experimental__DecodeInto(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken)
+ ///
+ /// Decodes and downscales the image from the specified stream if possible.
+ ///
+ /// The pixel format.
+ /// Stream.
+ /// Target size.
+ /// Cancellation token.
+ public Image 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);
- }
+ => this.Decode(stream, targetSize, cancellationToken);
// TODO: docs
- private Image Decode(BufferedReadStream stream, SpectralConverter converter, CancellationToken cancellationToken)
+ private Image Decode(BufferedReadStream stream, Size? targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- var scanDecoder = new HuffmanScanDecoder(stream, converter, cancellationToken);
+ using var spectralConverter = new SpectralConverter(this.Configuration, targetSize);
+ var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
this.ParseStream(stream, scanDecoder, cancellationToken);
this.InitExifProfile();
@@ -213,7 +214,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return new Image(
this.Configuration,
- converter.GetPixelBuffer(cancellationToken),
+ spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
}