Browse Source

Code cleanup, removed invalid second pass logic, marked scaled decoding internal

pull/2076/head
Dmitry Pentin 4 years ago
parent
commit
7057245177
  1. 57
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  2. 153
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  3. 29
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  4. 27
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

57
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -10,6 +10,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
internal abstract class SpectralConverter
{
/// <summary>
/// Supported scaled spectral block sizes for scaled IDCT decoding.
/// </summary>
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,
};
/// <summary>
/// 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
/// <param name="jpegData">The raw JPEG data.</param>
/// <returns>The color converter.</returns>
protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision);
/// <summary>
/// Calculates image size with optional scaling.
/// </summary>
/// <remarks>
/// Does not apply scalling if <paramref name="targetSize"/> is null.
/// </remarks>
/// <param name="size">Size of the image.</param>
/// <param name="targetSize">Target size of the image.</param>
/// <param name="blockPixelSize">Spectral block size, equals to 8 if scaling is not applied.</param>
/// <returns>Resulting image size, equals to <paramref name="size"/> if scaling is not applied.</returns>
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;
}
}
}

153
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -25,24 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Supported scaling factors for DCT jpeg scaling.
/// </summary>
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,
};
/// <summary>
/// <see cref="Configuration"/> instance associated with current
/// decoding routine.
@ -126,81 +108,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
var buffer = this.pixelBuffer;
Buffer2D<TPixel> buffer = this.pixelBuffer;
this.pixelBuffer = null;
return buffer;
}
/// <summary>
/// Calculates resulting image size.
/// </summary>
/// <remarks>
/// If <paramref name="targetSize"/> is null, unchanged <paramref name="size"/> is returned.
/// </remarks>
/// <param name="size">Size of the image.</param>
/// <param name="targetSize">Target image size, can be null.</param>
/// <param name="outputBlockSize">Scaled spectral block size if IDCT scaling should be applied</param>
/// <returns>Scaled jpeg image size.</returns>
// 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;
}
/// <summary>
/// Converts single spectral jpeg stride to color stride.
/// </summary>
@ -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<TPixel>(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<byte>(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;
}
/// <inheritdoc/>
public void Dispose()
{

29
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
/// <param name="targetSize">Placeholder4</param>
/// <param name="cancellationToken">Placeholder5</param>
/// <returns>Placeholder6</returns>
public Image Experimental__DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
=> this.Experimental__DecodeInto<Rgb24>(configuration, stream, targetSize, cancellationToken);
internal Image DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
=> this.DecodeInto<Rgb24>(configuration, stream, targetSize, cancellationToken);
/// <summary>
/// Placeholder summary.
/// Decodes and downscales the image from the specified stream if possible.
/// </summary>
/// <typeparam name="TPixel">Placeholder1</typeparam>
/// <param name="configuration">Placeholder2</param>
/// <param name="stream">Placeholder3</param>
/// <param name="targetSize">Placeholder4</param>
/// <param name="cancellationToken">Placeholder5</param>
/// <returns>Placeholder6</returns>
public Image<TPixel> Experimental__DecodeInto<TPixel>(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">Configuration.</param>
/// <param name="stream">Stream.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="cancellationToken">Cancellation token.</param>
internal Image<TPixel> DecodeInto<TPixel>(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedReadStream = new BufferedReadStream(configuration, stream);
try
{
Image<TPixel> img = decoder.Experimental__DecodeInto<TPixel>(bufferedReadStream, targetSize, cancellationToken);
if (img.Size() != targetSize)
{
img.Mutate(ctx => ctx.Resize(targetSize, KnownResamplers.Box, compand: false));
}
return img;
return decoder.DecodeInto<TPixel>(bufferedReadStream, targetSize, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{

27
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -185,24 +185,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration);
return this.Decode(stream, spectralConverter, cancellationToken);
}
=> this.Decode<TPixel>(stream, targetSize: null, cancellationToken);
// TODO: docs
public Image<TPixel> Experimental__DecodeInto<TPixel>(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken)
/// <summary>
/// Decodes and downscales the image from the specified stream if possible.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">Stream.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public Image<TPixel> DecodeInto<TPixel>(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration, targetSize);
return this.Decode(stream, spectralConverter, cancellationToken);
}
=> this.Decode<TPixel>(stream, targetSize, cancellationToken);
// TODO: docs
private Image<TPixel> Decode<TPixel>(BufferedReadStream stream, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
private Image<TPixel> Decode<TPixel>(BufferedReadStream stream, Size? targetSize, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var scanDecoder = new HuffmanScanDecoder(stream, converter, cancellationToken);
using var spectralConverter = new SpectralConverter<TPixel>(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<TPixel>(
this.Configuration,
converter.GetPixelBuffer(cancellationToken),
spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
}

Loading…
Cancel
Save