diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
index a09c7ada3e..70a4465121 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
@@ -18,11 +18,24 @@ internal class HuffmanScanDecoder
{
private readonly BufferedReadStream stream;
- // Frame related
+ ///
+ /// instance containing decoding-related information.
+ ///
private JpegFrame frame;
+
+ ///
+ /// Shortcut for .Components.
+ ///
private JpegComponent[] components;
- // The restart interval.
+ ///
+ /// Number of component in the current scan.
+ ///
+ private int componentsCount;
+
+ ///
+ /// The reset interval determined by RST markers.
+ ///
private int restartInterval;
// How many mcu's are left to do.
@@ -31,6 +44,16 @@ internal class HuffmanScanDecoder
// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
private int eobrun;
+ ///
+ /// The DC Huffman tables.
+ ///
+ private readonly HuffmanTable[] dcHuffmanTables;
+
+ ///
+ /// The AC Huffman tables
+ ///
+ private readonly HuffmanTable[] acHuffmanTables;
+
// The unzig data.
private ZigZag dctZigZag;
@@ -55,14 +78,16 @@ public HuffmanScanDecoder(
this.stream = stream;
this.spectralConverter = converter;
this.cancellationToken = cancellationToken;
- }
- // huffman tables
- public HuffmanTable[] DcHuffmanTables { get; set; }
-
- public HuffmanTable[] AcHuffmanTables { get; set; }
+ // TODO: this is actually a variable value depending on component count
+ const int maxTables = 4;
+ this.dcHuffmanTables = new HuffmanTable[maxTables];
+ this.acHuffmanTables = new HuffmanTable[maxTables];
+ }
- // Reset interval
+ ///
+ /// Sets reset interval determined by RST markers.
+ ///
public int ResetInterval
{
set
@@ -72,9 +97,6 @@ public int ResetInterval
}
}
- // The number of interleaved components.
- public int ComponentsLength { get; set; }
-
// The spectral selection start.
public int SpectralStart { get; set; }
@@ -90,10 +112,12 @@ public int ResetInterval
///
/// Decodes the entropy coded data.
///
- public void ParseEntropyCodedData()
+ public void ParseEntropyCodedData(int componentCount)
{
this.cancellationToken.ThrowIfCancellationRequested();
+ this.componentsCount = componentCount;
+
this.scanBuffer = new HuffmanScanBuffer(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
@@ -124,7 +148,7 @@ public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
private void ParseBaselineData()
{
- if (this.ComponentsLength == this.frame.ComponentCount)
+ if (this.componentsCount == this.frame.ComponentCount)
{
this.ParseBaselineDataInterleaved();
}
@@ -143,13 +167,13 @@ private void ParseBaselineDataInterleaved()
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks.
- for (int i = 0; i < this.ComponentsLength; i++)
+ for (int i = 0; i < this.componentsCount; i++)
{
int order = this.frame.ComponentOrder[i];
JpegComponent component = this.components[order];
- ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
dcHuffmanTable.Configure();
acHuffmanTable.Configure();
}
@@ -163,13 +187,13 @@ private void ParseBaselineDataInterleaved()
{
// Scan an interleaved mcu... process components in order
int mcuCol = mcu % mcusPerLine;
- for (int k = 0; k < this.ComponentsLength; k++)
+ for (int k = 0; k < this.componentsCount; k++)
{
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
- ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@@ -221,8 +245,8 @@ private void ParseBaselineDataNonInterleaved()
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
- ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
dcHuffmanTable.Configure();
acHuffmanTable.Configure();
@@ -272,7 +296,7 @@ private void CheckProgressiveData()
}
// AC scans may have only one component.
- if (this.ComponentsLength != 1)
+ if (this.componentsCount != 1)
{
invalid = true;
}
@@ -304,7 +328,7 @@ private void ParseProgressiveData()
{
this.CheckProgressiveData();
- if (this.ComponentsLength == 1)
+ if (this.componentsCount == 1)
{
this.ParseProgressiveDataNonInterleaved();
}
@@ -323,11 +347,11 @@ private void ParseProgressiveDataInterleaved()
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks.
- for (int k = 0; k < this.ComponentsLength; k++)
+ for (int k = 0; k < this.componentsCount; k++)
{
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
- ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure();
}
@@ -338,11 +362,11 @@ private void ParseProgressiveDataInterleaved()
// Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
- for (int k = 0; k < this.ComponentsLength; k++)
+ for (int k = 0; k < this.componentsCount; k++)
{
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
- ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@@ -390,7 +414,7 @@ private void ParseProgressiveDataNonInterleaved()
if (this.SpectralStart == 0)
{
- ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure();
for (int j = 0; j < h; j++)
@@ -418,7 +442,7 @@ ref Unsafe.Add(ref blockRef, i),
}
else
{
- ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
acHuffmanTable.Configure();
for (int j = 0; j < h; j++)
@@ -722,5 +746,19 @@ private bool HandleRestart()
return false;
}
+
+ ///
+ /// Build the huffman table using code lengths and code values.
+ ///
+ /// Table type.
+ /// Table index.
+ /// Code lengths.
+ /// Code values.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values)
+ {
+ HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
+ tables[index] = new HuffmanTable(codeLengths, values);
+ }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
index b1ac1f78f5..391dac784f 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
@@ -11,26 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
internal interface IRawJpegData : IDisposable
{
- ///
- /// Gets the image size in pixels.
- ///
- Size ImageSizeInPixels { get; }
-
- ///
- /// Gets the number of components.
- ///
- int ComponentCount { get; }
-
///
/// Gets the color space
///
JpegColorSpace ColorSpace { get; }
- ///
- /// Gets the number of bits used for precision.
- ///
- int Precision { get; }
-
///
/// Gets the components.
///
@@ -41,4 +26,4 @@ internal interface IRawJpegData : IDisposable
///
Block8x8F[] QuantizationTables { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
index e0311dafef..7cfbaddcc1 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
@@ -38,11 +38,6 @@ internal struct JpegBlockPostProcessor
///
private Size subSamplingDivisors;
- ///
- /// Defines the maximum value derived from the bitdepth.
- ///
- private readonly int maximumValue;
-
///
/// Initializes a new instance of the struct.
///
@@ -53,7 +48,6 @@ public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]);
this.subSamplingDivisors = component.SubSamplingDivisors;
- this.maximumValue = (int)MathF.Pow(2, decoder.Precision) - 1;
this.SourceBlock = default;
this.WorkspaceBlock1 = default;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
index 614e96e54a..ba3dfb6296 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
@@ -106,20 +106,24 @@ public void Dispose()
this.SpectralBlocks = null;
}
- public void Init()
+ ///
+ /// Initializes component for future buffers initialization.
+ ///
+ /// Maximal horizontal subsampling factor among all the components.
+ /// Maximal vertical subsampling factor among all the components.
+ public void Init(int maxSubFactorH, int maxSubFactorV)
{
this.WidthInBlocks = (int)MathF.Ceiling(
- MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor);
+ MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH);
this.HeightInBlocks = (int)MathF.Ceiling(
- MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor);
+ MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV);
int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor;
int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor;
this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu);
- JpegComponent c0 = this.Frame.Components[0];
- this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
+ this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors);
if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0)
{
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
index 79965a3f0c..9a659d6216 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
@@ -21,11 +21,18 @@ internal class JpegComponentPostProcessor : IDisposable
///
private readonly Size blockAreaSize;
+ ///
+ /// Jpeg frame instance containing required decoding metadata.
+ ///
+ private readonly JpegFrame frame;
+
///
/// Initializes a new instance of the class.
///
- public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
+ public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
{
+ this.frame = frame;
+
this.Component = component;
this.RawJpeg = rawJpeg;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
@@ -70,7 +77,8 @@ public void CopyBlocksToColorBuffer(int step)
Buffer2D spectralBuffer = this.Component.SpectralBlocks;
var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component);
- float maximumValue = MathF.Pow(2, this.RawJpeg.Precision) - 1;
+
+ float maximumValue = this.frame.MaxColorChannelValue;
int destAreaStride = this.ColorBuffer.Width;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
index 3a136b4103..fc109be261 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
@@ -10,15 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
internal sealed class JpegFrame : IDisposable
{
+ public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
+ {
+ this.Extended = sofMarker.Marker == JpegConstants.Markers.SOF1;
+ this.Progressive = sofMarker.Marker == JpegConstants.Markers.SOF2;
+
+ this.Precision = precision;
+ this.MaxColorChannelValue = MathF.Pow(2, precision) - 1;
+
+ this.PixelWidth = width;
+ this.PixelHeight = height;
+
+ this.ComponentCount = componentCount;
+ }
+
///
- /// Gets or sets a value indicating whether the frame uses the extended specification.
+ /// Gets a value indicating whether the frame uses the extended specification.
///
- public bool Extended { get; set; }
+ public bool Extended { get; private set; }
///
- /// Gets or sets a value indicating whether the frame uses the progressive specification.
+ /// Gets a value indicating whether the frame uses the progressive specification.
///
- public bool Progressive { get; set; }
+ public bool Progressive { get; private set; }
///
/// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers).
@@ -29,24 +43,34 @@ internal sealed class JpegFrame : IDisposable
public bool MultiScan { get; set; }
///
- /// Gets or sets the precision.
+ /// Gets the precision.
+ ///
+ public byte Precision { get; private set; }
+
+ ///
+ /// Gets the maximum color value derived from .
+ ///
+ public float MaxColorChannelValue { get; private set; }
+
+ ///
+ /// Gets the number of pixel per row.
///
- public byte Precision { get; set; }
+ public int PixelHeight { get; private set; }
///
- /// Gets or sets the number of scanlines within the frame.
+ /// Gets the number of pixels per line.
///
- public int PixelHeight { get; set; }
+ public int PixelWidth { get; private set; }
///
- /// Gets or sets the number of samples per scanline.
+ /// Gets the pixel size of the image.
///
- public int PixelWidth { get; set; }
+ public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight);
///
- /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4.
+ /// Gets the number of components within a frame.
///
- public byte ComponentCount { get; set; }
+ public byte ComponentCount { get; private set; }
///
/// Gets or sets the component id collection.
@@ -65,24 +89,24 @@ internal sealed class JpegFrame : IDisposable
public JpegComponent[] Components { get; set; }
///
- /// Gets or sets the maximum horizontal sampling factor.
+ /// Gets or sets the number of MCU's per line.
///
- public int MaxHorizontalFactor { get; set; }
+ public int McusPerLine { get; set; }
///
- /// Gets or sets the maximum vertical sampling factor.
+ /// Gets or sets the number of MCU's per column.
///
- public int MaxVerticalFactor { get; set; }
+ public int McusPerColumn { get; set; }
///
- /// Gets or sets the number of MCU's per line.
+ /// Gets the mcu size of the image.
///
- public int McusPerLine { get; set; }
+ public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn);
///
- /// Gets or sets the number of MCU's per column.
+ /// Gets the color depth, in number of bits per pixel.
///
- public int McusPerColumn { get; set; }
+ public int BitsPerPixel => this.ComponentCount * this.Precision;
///
public void Dispose()
@@ -101,15 +125,17 @@ public void Dispose()
///
/// Allocates the frame component blocks.
///
- public void InitComponents()
+ /// Maximal horizontal subsampling factor among all the components.
+ /// Maximal vertical subsampling factor among all the components.
+ public void Init(int maxSubFactorH, int maxSubFactorV)
{
- this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)this.MaxHorizontalFactor * 8);
- this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)this.MaxVerticalFactor * 8);
+ this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8);
+ this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
- component.Init();
+ component.Init(maxSubFactorH, maxSubFactorV);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
index 9f3d4195cc..50cfa0188a 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
@@ -78,7 +78,7 @@ public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
- this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, jpegData, postProcessorBufferSize, frame.Components[i]);
+ this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
}
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 922e9797cb..77b1b44aff 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -30,7 +30,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
///
/// The only supported precision
///
- private readonly int[] supportedPrecisions = { 8, 12 };
+ private readonly byte[] supportedPrecisions = { 8, 12 };
///
/// The buffer used to temporarily store bytes read from the stream.
@@ -42,21 +42,6 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
///
private readonly byte[] markerBuffer = new byte[2];
- ///
- /// The DC Huffman tables.
- ///
- private HuffmanTable[] dcHuffmanTables;
-
- ///
- /// The AC Huffman tables
- ///
- private HuffmanTable[] acHuffmanTables;
-
- ///
- /// The reset interval determined by RST markers.
- ///
- private ushort resetInterval;
-
///
/// Whether the image has an EXIF marker.
///
@@ -122,30 +107,7 @@ public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
public JpegFrame Frame { get; private set; }
///
- public Size ImageSizeInPixels { get; private set; }
-
- ///
- Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels;
-
- ///
- /// Gets the number of MCU blocks in the image as .
- ///
- public Size ImageSizeInMCU { get; private set; }
-
- ///
- /// Gets the image width
- ///
- public int ImageWidth => this.ImageSizeInPixels.Width;
-
- ///
- /// Gets the image height
- ///
- public int ImageHeight => this.ImageSizeInPixels.Height;
-
- ///
- /// Gets the color depth, in number of bits per pixel.
- ///
- public int BitsPerPixel => this.ComponentCount * this.Frame.Precision;
+ Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize;
///
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
@@ -157,15 +119,9 @@ public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
///
public ImageMetadata Metadata { get; private set; }
- ///
- public int ComponentCount { get; private set; }
-
///
public JpegColorSpace ColorSpace { get; private set; }
- ///
- public int Precision { get; private set; }
-
///
/// Gets the components.
///
@@ -240,7 +196,8 @@ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancella
this.InitIptcProfile();
this.InitDerivedMetadataProperties();
- return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata);
+ Size pixelSize = this.Frame.PixelSize;
+ return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
}
///
@@ -270,14 +227,6 @@ internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDeco
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
this.QuantizationTables = new Block8x8F[4];
- // Only assign what we need
- if (!metadataOnly)
- {
- const int maxTables = 4;
- this.dcHuffmanTables = new HuffmanTable[maxTables];
- this.acHuffmanTables = new HuffmanTable[maxTables];
- }
-
// Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695
while (fileMarker.Marker != JpegConstants.Markers.EOI
@@ -301,7 +250,7 @@ internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDeco
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
- this.ProcessStartOfScanMarker(stream, cancellationToken);
+ this.ProcessStartOfScanMarker(stream, remaining, cancellationToken);
break;
}
else
@@ -392,22 +341,21 @@ public void Dispose()
// Set large fields to null.
this.Frame = null;
- this.dcHuffmanTables = null;
- this.acHuffmanTables = null;
+ this.scanDecoder = null;
}
///
/// Returns the correct colorspace based on the image component count
///
/// The
- private JpegColorSpace DeduceJpegColorSpace()
+ private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
{
- if (this.ComponentCount == 1)
+ if (componentCount == 1)
{
return JpegColorSpace.Grayscale;
}
- if (this.ComponentCount == 3)
+ if (componentCount == 3)
{
if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
@@ -419,14 +367,14 @@ private JpegColorSpace DeduceJpegColorSpace()
return JpegColorSpace.YCbCr;
}
- if (this.ComponentCount == 4)
+ if (componentCount == 4)
{
return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck
? JpegColorSpace.Ycck
: JpegColorSpace.Cmyk;
}
- JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}");
+ JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {componentCount}");
return default;
}
@@ -565,7 +513,7 @@ private void ProcessApp1Marker(BufferedReadStream stream, int remaining)
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
}
- var profile = new byte[remaining];
+ byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining);
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
@@ -599,14 +547,14 @@ private void ProcessApp2Marker(BufferedReadStream stream, int remaining)
return;
}
- var identifier = new byte[Icclength];
+ byte[] identifier = new byte[Icclength];
stream.Read(identifier, 0, Icclength);
remaining -= Icclength; // We have read it by this point
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{
this.isIcc = true;
- var profile = new byte[remaining];
+ byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining);
if (this.iccData is null)
@@ -644,7 +592,7 @@ private void ProcessApp13Marker(BufferedReadStream stream, int remaining)
remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker))
{
- var resourceBlockData = new byte[remaining];
+ byte[] resourceBlockData = new byte[remaining];
stream.Read(resourceBlockData, 0, remaining);
Span blockDataSpan = resourceBlockData.AsSpan();
@@ -659,8 +607,8 @@ private void ProcessApp13Marker(BufferedReadStream stream, int remaining)
Span imageResourceBlockId = blockDataSpan.Slice(0, 2);
if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker))
{
- var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
- var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
+ int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
+ int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
int dataStartIdx = 2 + resourceBlockNameLength + 4;
if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize)
{
@@ -671,8 +619,8 @@ private void ProcessApp13Marker(BufferedReadStream stream, int remaining)
}
else
{
- var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
- var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
+ int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
+ int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
int dataStartIdx = 2 + resourceBlockNameLength + 4;
if (blockDataSpan.Length < dataStartIdx + resourceDataSize)
{
@@ -695,7 +643,7 @@ private void ProcessApp13Marker(BufferedReadStream stream, int remaining)
private static int ReadImageResourceNameLength(Span blockDataSpan)
{
byte nameLength = blockDataSpan[2];
- var nameDataSize = nameLength == 0 ? 2 : nameLength;
+ int nameDataSize = nameLength == 0 ? 2 : nameLength;
if (nameDataSize % 2 != 0)
{
nameDataSize++;
@@ -712,9 +660,7 @@ private static int ReadImageResourceNameLength(Span blockDataSpan)
/// The block length.
[MethodImpl(InliningOptions.ShortMethod)]
private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength)
- {
- return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
- }
+ => BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
///
/// Processes the application header containing the Adobe identifier
@@ -849,60 +795,62 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
}
- // Read initial marker definitions.
+ // Read initial marker definitions
const int length = 6;
stream.Read(this.temp, 0, length);
- // We only support 8-bit and 12-bit precision.
- if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1)
+ // 1 byte: Bits/sample precision
+ byte precision = this.temp[0];
+
+ // Validate: only 8-bit and 12-bit precisions are supported
+ if (Array.IndexOf(this.supportedPrecisions, precision) == -1)
{
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported.");
}
- this.Precision = this.temp[0];
+ // 2 byte: Height
+ int frameHeight = (this.temp[1] << 8) | this.temp[2];
- this.Frame = new JpegFrame
- {
- Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
- Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
- Precision = this.temp[0],
- PixelHeight = (this.temp[1] << 8) | this.temp[2],
- PixelWidth = (this.temp[3] << 8) | this.temp[4],
- ComponentCount = this.temp[5]
- };
-
- if (this.Frame.PixelWidth == 0 || this.Frame.PixelHeight == 0)
+ // 2 byte: Width
+ int frameWidth = (this.temp[3] << 8) | this.temp[4];
+
+ // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that)
+ if (frameHeight == 0 || frameWidth == 0)
{
- JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.PixelWidth, this.Frame.PixelHeight);
+ JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight);
}
- this.ImageSizeInPixels = new Size(this.Frame.PixelWidth, this.Frame.PixelHeight);
- this.ComponentCount = this.Frame.ComponentCount;
+ // 1 byte: Number of components
+ byte componentCount = this.temp[5];
+ this.ColorSpace = this.DeduceJpegColorSpace(componentCount);
- this.ColorSpace = this.DeduceJpegColorSpace();
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
+ this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
+
if (!metadataOnly)
{
remaining -= length;
+ // Validate: remaining part must be equal to components * 3
const int componentBytes = 3;
- if (remaining > this.ComponentCount * componentBytes)
+ if (remaining != componentCount * componentBytes)
{
JpegThrowHelper.ThrowBadMarker("SOFn", remaining);
}
+ // components*3 bytes: component data
stream.Read(this.temp, 0, remaining);
// No need to pool this. They max out at 4
- this.Frame.ComponentIds = new byte[this.ComponentCount];
- this.Frame.ComponentOrder = new byte[this.ComponentCount];
- this.Frame.Components = new JpegComponent[this.ComponentCount];
+ this.Frame.ComponentIds = new byte[componentCount];
+ this.Frame.ComponentOrder = new byte[componentCount];
+ this.Frame.Components = new JpegComponent[componentCount];
int maxH = 0;
int maxV = 0;
int index = 0;
- for (int i = 0; i < this.ComponentCount; i++)
+ for (int i = 0; i < componentCount; i++)
{
byte hv = this.temp[index + 1];
int h = (hv >> 4) & 15;
@@ -926,13 +874,8 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
index += componentBytes;
}
- this.Frame.MaxHorizontalFactor = maxH;
- this.Frame.MaxVerticalFactor = maxV;
- this.Frame.InitComponents();
+ this.Frame.Init(maxH, maxV);
- this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn);
-
- // This can be injected in SOF marker callback
this.scanDecoder.InjectFrameData(this.Frame, this);
}
}
@@ -996,8 +939,8 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem
i += 17 + codeLengthSum;
- this.BuildHuffmanTable(
- tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables,
+ this.scanDecoder.BuildHuffmanTable(
+ tableType,
tableIndex,
codeLengthsSpan,
huffmanValuesSpan);
@@ -1020,87 +963,101 @@ private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int r
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining);
}
- this.resetInterval = this.ReadUint16(stream);
+ this.scanDecoder.ResetInterval = this.ReadUint16(stream);
}
///
/// Processes the SOS (Start of scan marker).
///
- private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken)
+ private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining, CancellationToken cancellationToken)
{
if (this.Frame is null)
{
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
}
+ // 1 byte: Number of components in scan
int selectorsCount = stream.ReadByte();
+
+ // Validate: 0 < count <= totalComponents
+ if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount)
+ {
+ // TODO: extract as separate method?
+ JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}.");
+ }
+
+ // Validate: marker must contain exactly (4 + selectorsCount*2) bytes
+ int selectorsBytes = selectorsCount * 2;
+ if (remaining != 4 + selectorsBytes)
+ {
+ JpegThrowHelper.ThrowBadMarker("SOS", remaining);
+ }
+
+ // selectorsCount*2 bytes: component index + huffman tables indices
+ stream.Read(this.temp, 0, selectorsBytes);
+
this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount;
- for (int i = 0; i < selectorsCount; i++)
+ for (int i = 0; i < selectorsBytes; i += 2)
{
- int componentIndex = -1;
- int selector = stream.ReadByte();
+ // 1 byte: Component id
+ int componentSelectorId = this.temp[i];
+ int componentIndex = -1;
for (int j = 0; j < this.Frame.ComponentIds.Length; j++)
{
byte id = this.Frame.ComponentIds[j];
- if (selector == id)
+ if (componentSelectorId == id)
{
componentIndex = j;
break;
}
}
- if (componentIndex < 0)
+ // Validate: must be found among registered components
+ if (componentIndex == -1)
{
- JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}.");
+ // TODO: extract as separate method?
+ JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component id in scan: {componentSelectorId}.");
}
- ref JpegComponent component = ref this.Frame.Components[componentIndex];
- int tableSpec = stream.ReadByte();
- component.DCHuffmanTableId = tableSpec >> 4;
- component.ACHuffmanTableId = tableSpec & 15;
- this.Frame.ComponentOrder[i] = (byte)componentIndex;
- }
+ this.Frame.ComponentOrder[i / 2] = (byte)componentIndex;
- stream.Read(this.temp, 0, 3);
+ JpegComponent component = this.Frame.Components[componentIndex];
- int spectralStart = this.temp[0];
- int spectralEnd = this.temp[1];
- int successiveApproximation = this.temp[2];
+ // 1 byte: Huffman table selectors.
+ // 4 bits - dc
+ // 4 bits - ac
+ int tableSpec = this.temp[i + 1];
+ int dcTableIndex = tableSpec >> 4;
+ int acTableIndex = tableSpec & 15;
- // All the comments below are for separate refactoring PR
- // Main reason it's not fixed here is to make this commit less intrusive
-
- // Huffman tables can be calculated directly in the scan decoder class
- this.scanDecoder.DcHuffmanTables = this.dcHuffmanTables;
- this.scanDecoder.AcHuffmanTables = this.acHuffmanTables;
+ // Validate: both must be < 4
+ if (dcTableIndex >= 4 || acTableIndex >= 4)
+ {
+ // TODO: extract as separate method?
+ JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}");
+ }
- // This can be injectd in DRI marker callback
- this.scanDecoder.ResetInterval = this.resetInterval;
+ component.DCHuffmanTableId = dcTableIndex;
+ component.ACHuffmanTableId = acTableIndex;
+ }
- // This can be passed as ParseEntropyCodedData() parameter as it is used only there
- this.scanDecoder.ComponentsLength = selectorsCount;
+ // 3 bytes: Progressive scan decoding data
+ stream.Read(this.temp, 0, 3);
- // This is okay to inject here, might be good to wrap it in a separate struct but not really necessary
+ int spectralStart = this.temp[0];
this.scanDecoder.SpectralStart = spectralStart;
+
+ int spectralEnd = this.temp[1];
this.scanDecoder.SpectralEnd = spectralEnd;
+
+ int successiveApproximation = this.temp[2];
this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4;
this.scanDecoder.SuccessiveLow = successiveApproximation & 15;
- this.scanDecoder.ParseEntropyCodedData();
+ this.scanDecoder.ParseEntropyCodedData(selectorsCount);
}
- ///
- /// Builds the huffman tables
- ///
- /// The tables
- /// The table index
- /// The codelengths
- /// The values
- [MethodImpl(InliningOptions.ShortMethod)]
- private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values)
- => tables[index] = new HuffmanTable(codeLengths, values);
-
///
/// Reads a from the stream advancing it by two bytes
///
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
index 304dd93a63..d12240cba3 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
@@ -87,7 +87,9 @@ public partial class JpegDecoderTests
TestImages.Jpeg.Issues.Fuzz.ArgumentException826B,
TestImages.Jpeg.Issues.Fuzz.ArgumentException826C,
TestImages.Jpeg.Issues.Fuzz.AccessViolationException827,
- TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839
+ TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839,
+ TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A,
+ TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B
};
private static readonly Dictionary CustomToleranceValues =
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index a052ee88a5..674aa6d8f6 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -62,10 +62,7 @@ private static bool SkipTest(ITestImageProvider provider)
return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription);
}
- public JpegDecoderTests(ITestOutputHelper output)
- {
- this.Output = output;
- }
+ public JpegDecoderTests(ITestOutputHelper output) => this.Output = output;
private ITestOutputHelper Output { get; }
@@ -163,7 +160,7 @@ public async Task Identify_IsCancellable()
{
var cts = new CancellationTokenSource();
- var file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
+ string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s =>
{
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
index a124ec1918..0a4d85344b 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
@@ -43,12 +43,12 @@ public void ComponentScalingIsCorrect_1ChannelJpeg()
{
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400))
{
- Assert.Equal(1, decoder.ComponentCount);
+ Assert.Equal(1, decoder.Frame.ComponentCount);
Assert.Equal(1, decoder.Components.Length);
- Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8);
+ Size expectedSizeInBlocks = decoder.Frame.PixelSize.DivideRoundUp(8);
- Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU);
+ Assert.Equal(expectedSizeInBlocks, decoder.Frame.McuSize);
var uniform1 = new Size(1, 1);
JpegComponent c0 = decoder.Components[0];
@@ -70,7 +70,7 @@ public void PrintComponentData(string imageFile)
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{
sb.AppendLine(imageFile);
- sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}");
+ sb.AppendLine($"Size:{decoder.Frame.PixelSize} MCU:{decoder.Frame.McuSize}");
JpegComponent c0 = decoder.Components[0];
JpegComponent c1 = decoder.Components[1];
@@ -106,7 +106,7 @@ public void ComponentScalingIsCorrect_MultiChannelJpeg(
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{
- Assert.Equal(componentCount, decoder.ComponentCount);
+ Assert.Equal(componentCount, decoder.Frame.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length);
JpegComponent c0 = decoder.Components[0];
@@ -115,7 +115,7 @@ public void ComponentScalingIsCorrect_MultiChannelJpeg(
var uniform1 = new Size(1, 1);
- Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma);
+ Size expectedLumaSizeInBlocks = decoder.Frame.McuSize.MultiplyBy(fLuma);
Size divisor = fLuma.DivideBy(fChroma);
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 6d2f65f575..fac8cb4a32 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -261,6 +261,8 @@ public static class Fuzz
public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg";
public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg";
public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg";
+ public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg";
+ public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg";
}
}
diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg
new file mode 100644
index 0000000000..eb8fb9010a
--- /dev/null
+++ b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fbb6acd612cdb09825493d04ec7c6aba8ef2a94cc9a86c6b16218720adfb8f5c
+size 58065
diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg
new file mode 100644
index 0000000000..7dd4285914
--- /dev/null
+++ b/tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8720a9ccf118c3f55407aa250ee490d583286c7e40c8c62a6f8ca449ca3ddff3
+size 58067