From 70572451774e63264c627511136c27665312c8d1 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 26 Apr 2022 14:52:11 +0300 Subject: [PATCH] Code cleanup, removed invalid second pass logic, marked scaled decoding internal --- .../Components/Decoder/SpectralConverter.cs | 57 +++++++ .../Decoder/SpectralConverter{TPixel}.cs | 153 +++--------------- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 29 ++-- .../Formats/Jpeg/JpegDecoderCore.cs | 27 ++-- 4 files changed, 105 insertions(+), 161 deletions(-) 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); }