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);
}