From 95c56b093662e6939bb0ea2fd57ba0ae90ef362d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 1 May 2022 16:38:21 +0300 Subject: [PATCH] Small bug fixes, ready for merging --- .../Decoder/ArithmeticScanDecoder.cs | 5 +-- .../Components/Decoder/HuffmanScanDecoder.cs | 25 ++++--------- .../Components/Decoder/SpectralConverter.cs | 13 +++++-- .../Decoder/SpectralConverter{TPixel}.cs | 25 ++++++++++--- .../Formats/Jpeg/JpegDecoderCore.cs | 35 +++++++++++-------- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 4 +++ .../Formats/Jpg/SpectralJpegTests.cs | 11 ++++-- 7 files changed, 72 insertions(+), 46 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs index d3a5ea15b..183b55aca 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs @@ -247,8 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.scanBuffer = new JpegBitReader(this.stream); - bool fullScan = this.frame.Progressive || this.frame.MultiScan; - this.frame.AllocateComponents(fullScan); + this.frame.AllocateComponents(); if (this.frame.Progressive) { @@ -326,11 +325,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder if (this.scanComponentCount != 1) { + this.spectralConverter.PrepareForDecoding(); this.ParseBaselineDataInterleaved(); this.spectralConverter.CommitConversion(); } else if (this.frame.ComponentCount == 1) { + this.spectralConverter.PrepareForDecoding(); this.ParseBaselineDataSingleComponent(); this.spectralConverter.CommitConversion(); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index fa18ae911..fc5ded3ac 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -109,13 +109,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // The successive approximation low bit end. public int SuccessiveLow { get; set; } - /// - /// Decodes the entropy coded data. - /// - /// Component count in the current scan. - /// Frame containing decoding data about the frame. - /// Decoding data about the jpeg. - public void ParseEntropyCodedData(int scanComponentCount, JpegFrame frame, IRawJpegData jpegData) + /// + public void ParseEntropyCodedData(int scanComponentCount) { this.cancellationToken.ThrowIfCancellationRequested(); @@ -123,17 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.scanBuffer = new JpegBitReader(this.stream); - // Decoder can encounter markers which would alter parameters - // needed for spectral buffers allocation and for spectral - // converter allocation - if (this.frame == null) - { - frame.AllocateComponents(); - - this.frame = frame; - this.components = frame.Components; - this.spectralConverter.InjectFrameData(frame, jpegData); - } + this.frame.AllocateComponents(); if (!this.frame.Progressive) { @@ -163,11 +148,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (this.scanComponentCount != 1) { + this.spectralConverter.PrepareForDecoding(); this.ParseBaselineDataInterleaved(); this.spectralConverter.CommitConversion(); } else if (this.frame.ComponentCount == 1) { + this.spectralConverter.PrepareForDecoding(); this.ParseBaselineDataSingleComponent(); this.spectralConverter.CommitConversion(); } @@ -280,7 +267,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private void ParseBaselineDataSingleComponent() { - var component = this.frame.Components[0] as JpegComponent; + JpegComponent component = this.frame.Components[0]; int mcuLines = this.frame.McusPerColumn; int w = component.WidthInBlocks; int h = component.SamplingFactors.Height; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index 9b1eaaf47..be65bc0d6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -35,12 +35,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Injects jpeg image decoding metadata. /// /// - /// This is guaranteed to be called only once at SOF marker by . + /// This should be called exactly once during SOF (Start Of Frame) marker. /// - /// instance containing decoder-specific parameters. - /// instance containing decoder-specific parameters. + /// Instance containing decoder-specific parameters. + /// Instance containing decoder-specific parameters. public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); + /// + /// Initializes this spectral decoder instance for decoding. + /// This should be called exactly once after all markers which can alter + /// spectral decoding parameters. + /// + public abstract void PrepareForDecoding(); + /// /// Converts single spectral jpeg stride to color stride in baseline /// decoding mode. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 820c03d12..f16f176df 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -31,6 +31,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// private readonly Configuration configuration; + private JpegFrame frame; + + private IRawJpegData jpegData; + /// /// Jpeg component converters from decompressed spectral to color data. /// @@ -99,6 +103,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (!this.Converted) { + this.PrepareForDecoding(); + int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); for (int step = 0; step < steps; step++) @@ -166,18 +172,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { + this.frame = frame; + this.jpegData = jpegData; + } + + /// + public override void PrepareForDecoding() + { + DebugGuard.IsTrue(this.colorConverter == null, "SpectralConverter.PrepareForDecoding() must be called once."); + MemoryAllocator allocator = this.configuration.MemoryAllocator; // color converter from RGB to TPixel - JpegColorConverterBase converter = this.GetColorConverter(frame, jpegData); + JpegColorConverterBase converter = this.GetColorConverter(this.frame, this.jpegData); this.colorConverter = converter; // resulting image size - Size pixelSize = CalculateResultingImageSize(frame.PixelSize, this.targetSize, out int blockPixelSize); + Size pixelSize = CalculateResultingImageSize(this.frame.PixelSize, this.targetSize, out int blockPixelSize); // iteration data - int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); - int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); + int majorBlockWidth = this.frame.Components.Max((component) => component.SizeInBlocks.Width); + int majorVerticalSamplingFactor = this.frame.Components.Max((component) => component.SamplingFactors.Height); this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize; @@ -193,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int batchSize = converter.ElementsPerBatch; int batchRemainder = bufferWidth & (batchSize - 1); var postProcessorBufferSize = new Size(bufferWidth + (batchSize - batchRemainder), this.pixelRowsPerStep); - this.componentProcessors = this.CreateComponentProcessors(frame, jpegData, blockPixelSize, postProcessorBufferSize); + this.componentProcessors = this.CreateComponentProcessors(this.frame, this.jpegData, blockPixelSize, postProcessorBufferSize); // single 'stride' rgba32 buffer for conversion between spectral and TPixel this.rgbBuffer = allocator.Allocate(pixelSize.Width * 3); diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a227a3abf..038c23a7e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -198,6 +198,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg where TPixel : unmanaged, IPixel => this.Decode(stream, targetSize: null, cancellationToken); + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ParseStream(stream, spectralConverter: null, cancellationToken); + this.InitExifProfile(); + this.InitIccProfile(); + this.InitIptcProfile(); + this.InitXmpProfile(); + this.InitDerivedMetadataProperties(); + + Size pixelSize = this.Frame.PixelSize; + return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); + } + /// /// Decodes and downscales the image from the specified stream if possible. /// @@ -205,10 +219,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Stream. /// Target size. /// Cancellation token. - public Image DecodeInto(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken) + internal Image DecodeInto(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => this.Decode(stream, targetSize, cancellationToken); + private Image Decode(BufferedReadStream stream, Size? targetSize, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using var spectralConverter = new SpectralConverter(this.Configuration, targetSize); this.ParseStream(stream, spectralConverter, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); @@ -222,20 +240,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Metadata); } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ParseStream(stream, spectralConverter: null, cancellationToken); - this.InitExifProfile(); - this.InitIccProfile(); - this.InitIptcProfile(); - this.InitXmpProfile(); - this.InitDerivedMetadataProperties(); - - Size pixelSize = this.Frame.PixelSize; - return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); - } - /// /// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's, /// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips). @@ -1263,6 +1267,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (!metadataOnly) { this.Frame.Init(maxH, maxV); + this.scanDecoder.InjectFrameData(this.Frame, this); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 450c786ad..5a06db2a6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -57,6 +57,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { } + + public override void PrepareForDecoding() + { + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index c4a448ff8..ab0ff8dd4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -141,6 +141,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { private JpegFrame frame; + private IRawJpegData jpegData; + private LibJpegTools.SpectralData spectralData; private int baselineScanRowCounter; @@ -153,6 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Progressive and multi-scan images must be loaded manually if (this.frame.Progressive || this.frame.MultiScan) { + this.PrepareForDecoding(); LibJpegTools.ComponentData[] components = this.spectralData.Components; for (int i = 0; i < components.Length; i++) { @@ -190,11 +193,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { this.frame = frame; + this.jpegData = jpegData; + } - var spectralComponents = new LibJpegTools.ComponentData[frame.ComponentCount]; + public override void PrepareForDecoding() + { + var spectralComponents = new LibJpegTools.ComponentData[this.frame.ComponentCount]; for (int i = 0; i < spectralComponents.Length; i++) { - var component = frame.Components[i] as JpegComponent; + JpegComponent component = this.frame.Components[i]; spectralComponents[i] = new LibJpegTools.ComponentData(component.WidthInBlocks, component.HeightInBlocks, component.Index); }