Browse Source

Merge pull request #1702 from br3aker/jpeg-decoder-refactoring

Jpeg decoder refactoring, fixed 1693
pull/1709/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
ce2a28b632
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 96
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  2. 17
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  3. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  4. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  5. 12
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  6. 74
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  7. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  8. 253
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  9. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  10. 7
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  11. 12
      tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
  12. 2
      tests/ImageSharp.Tests/TestImages.cs
  13. 3
      tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg
  14. 3
      tests/Images/Input/Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg

96
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -18,11 +18,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
private readonly BufferedReadStream stream; private readonly BufferedReadStream stream;
// Frame related /// <summary>
/// <see cref="JpegFrame"/> instance containing decoding-related information.
/// </summary>
private JpegFrame frame; private JpegFrame frame;
/// <summary>
/// Shortcut for <see cref="frame"/>.Components.
/// </summary>
private JpegComponent[] components; private JpegComponent[] components;
// The restart interval. /// <summary>
/// Number of component in the current scan.
/// </summary>
private int componentsCount;
/// <summary>
/// The reset interval determined by RST markers.
/// </summary>
private int restartInterval; private int restartInterval;
// How many mcu's are left to do. // How many mcu's are left to do.
@ -31,6 +44,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
private int eobrun; private int eobrun;
/// <summary>
/// The DC Huffman tables.
/// </summary>
private readonly HuffmanTable[] dcHuffmanTables;
/// <summary>
/// The AC Huffman tables
/// </summary>
private readonly HuffmanTable[] acHuffmanTables;
// The unzig data. // The unzig data.
private ZigZag dctZigZag; private ZigZag dctZigZag;
@ -55,14 +78,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.stream = stream; this.stream = stream;
this.spectralConverter = converter; this.spectralConverter = converter;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
}
// huffman tables // TODO: this is actually a variable value depending on component count
public HuffmanTable[] DcHuffmanTables { get; set; } const int maxTables = 4;
this.dcHuffmanTables = new HuffmanTable[maxTables];
public HuffmanTable[] AcHuffmanTables { get; set; } this.acHuffmanTables = new HuffmanTable[maxTables];
}
// Reset interval /// <summary>
/// Sets reset interval determined by RST markers.
/// </summary>
public int ResetInterval public int ResetInterval
{ {
set set
@ -72,9 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
// The number of interleaved components.
public int ComponentsLength { get; set; }
// The spectral selection start. // The spectral selection start.
public int SpectralStart { get; set; } public int SpectralStart { get; set; }
@ -90,10 +112,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary> /// <summary>
/// Decodes the entropy coded data. /// Decodes the entropy coded data.
/// </summary> /// </summary>
public void ParseEntropyCodedData() public void ParseEntropyCodedData(int componentCount)
{ {
this.cancellationToken.ThrowIfCancellationRequested(); this.cancellationToken.ThrowIfCancellationRequested();
this.componentsCount = componentCount;
this.scanBuffer = new HuffmanScanBuffer(this.stream); this.scanBuffer = new HuffmanScanBuffer(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan; bool fullScan = this.frame.Progressive || this.frame.MultiScan;
@ -124,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineData() private void ParseBaselineData()
{ {
if (this.ComponentsLength == this.frame.ComponentCount) if (this.componentsCount == this.frame.ComponentCount)
{ {
this.ParseBaselineDataInterleaved(); this.ParseBaselineDataInterleaved();
} }
@ -143,13 +167,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks. // 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]; int order = this.frame.ComponentOrder[i];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
dcHuffmanTable.Configure(); dcHuffmanTable.Configure();
acHuffmanTable.Configure(); acHuffmanTable.Configure();
} }
@ -163,13 +187,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuCol = 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]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
int h = component.HorizontalSamplingFactor; int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor; int v = component.VerticalSamplingFactor;
@ -221,8 +245,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int w = component.WidthInBlocks; int w = component.WidthInBlocks;
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
dcHuffmanTable.Configure(); dcHuffmanTable.Configure();
acHuffmanTable.Configure(); acHuffmanTable.Configure();
@ -272,7 +296,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
// AC scans may have only one component. // AC scans may have only one component.
if (this.ComponentsLength != 1) if (this.componentsCount != 1)
{ {
invalid = true; invalid = true;
} }
@ -304,7 +328,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
this.CheckProgressiveData(); this.CheckProgressiveData();
if (this.ComponentsLength == 1) if (this.componentsCount == 1)
{ {
this.ParseProgressiveDataNonInterleaved(); this.ParseProgressiveDataNonInterleaved();
} }
@ -323,11 +347,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks. // 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]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure(); dcHuffmanTable.Configure();
} }
@ -338,11 +362,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine; int mcuRow = mcu / mcusPerLine;
int mcuCol = 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]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; 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 h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor; int v = component.VerticalSamplingFactor;
@ -390,7 +414,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (this.SpectralStart == 0) if (this.SpectralStart == 0)
{ {
ref HuffmanTable dcHuffmanTable = ref this.DcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure(); dcHuffmanTable.Configure();
for (int j = 0; j < h; j++) for (int j = 0; j < h; j++)
@ -418,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
else else
{ {
ref HuffmanTable acHuffmanTable = ref this.AcHuffmanTables[component.ACHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
acHuffmanTable.Configure(); acHuffmanTable.Configure();
for (int j = 0; j < h; j++) for (int j = 0; j < h; j++)
@ -722,5 +746,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return false; return false;
} }
/// <summary>
/// Build the huffman table using code lengths and code values.
/// </summary>
/// <param name="type">Table type.</param>
/// <param name="index">Table index.</param>
/// <param name="codeLengths">Code lengths.</param>
/// <param name="values">Code values.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void BuildHuffmanTable(int type, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
tables[index] = new HuffmanTable(codeLengths, values);
}
} }
} }

17
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -11,26 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal interface IRawJpegData : IDisposable internal interface IRawJpegData : IDisposable
{ {
/// <summary>
/// Gets the image size in pixels.
/// </summary>
Size ImageSizeInPixels { get; }
/// <summary>
/// Gets the number of components.
/// </summary>
int ComponentCount { get; }
/// <summary> /// <summary>
/// Gets the color space /// Gets the color space
/// </summary> /// </summary>
JpegColorSpace ColorSpace { get; } JpegColorSpace ColorSpace { get; }
/// <summary>
/// Gets the number of bits used for precision.
/// </summary>
int Precision { get; }
/// <summary> /// <summary>
/// Gets the components. /// Gets the components.
/// </summary> /// </summary>
@ -41,4 +26,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
Block8x8F[] QuantizationTables { get; } Block8x8F[] QuantizationTables { get; }
} }
} }

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -38,11 +38,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private Size subSamplingDivisors; private Size subSamplingDivisors;
/// <summary>
/// Defines the maximum value derived from the bitdepth.
/// </summary>
private readonly int maximumValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct. /// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary> /// </summary>
@ -53,7 +48,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int qtIndex = component.QuantizationTableIndex; int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]);
this.subSamplingDivisors = component.SubSamplingDivisors; this.subSamplingDivisors = component.SubSamplingDivisors;
this.maximumValue = (int)MathF.Pow(2, decoder.Precision) - 1;
this.SourceBlock = default; this.SourceBlock = default;
this.WorkspaceBlock1 = default; this.WorkspaceBlock1 = default;

14
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

@ -106,20 +106,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.SpectralBlocks = null; this.SpectralBlocks = null;
} }
public void Init() /// <summary>
/// Initializes component for future buffers initialization.
/// </summary>
/// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
public void Init(int maxSubFactorH, int maxSubFactorV)
{ {
this.WidthInBlocks = (int)MathF.Ceiling( 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( 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 blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor;
int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor;
this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu);
JpegComponent c0 = this.Frame.Components[0]; this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors);
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0)
{ {

12
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -21,11 +21,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private readonly Size blockAreaSize; private readonly Size blockAreaSize;
/// <summary>
/// Jpeg frame instance containing required decoding metadata.
/// </summary>
private readonly JpegFrame frame;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class. /// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary> /// </summary>
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.Component = component;
this.RawJpeg = rawJpeg; this.RawJpeg = rawJpeg;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8; this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
@ -70,7 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks; Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); 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; int destAreaStride = this.ColorBuffer.Width;

74
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -10,15 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal sealed class JpegFrame : IDisposable 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;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the extended specification. /// Gets a value indicating whether the frame uses the extended specification.
/// </summary> /// </summary>
public bool Extended { get; set; } public bool Extended { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the progressive specification. /// Gets a value indicating whether the frame uses the progressive specification.
/// </summary> /// </summary>
public bool Progressive { get; set; } public bool Progressive { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers). /// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers).
@ -29,24 +43,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public bool MultiScan { get; set; } public bool MultiScan { get; set; }
/// <summary> /// <summary>
/// Gets or sets the precision. /// Gets the precision.
/// </summary>
public byte Precision { get; private set; }
/// <summary>
/// Gets the maximum color value derived from <see cref="Precision"/>.
/// </summary>
public float MaxColorChannelValue { get; private set; }
/// <summary>
/// Gets the number of pixel per row.
/// </summary> /// </summary>
public byte Precision { get; set; } public int PixelHeight { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the number of scanlines within the frame. /// Gets the number of pixels per line.
/// </summary> /// </summary>
public int PixelHeight { get; set; } public int PixelWidth { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the number of samples per scanline. /// Gets the pixel size of the image.
/// </summary> /// </summary>
public int PixelWidth { get; set; } public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight);
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public byte ComponentCount { get; set; } public byte ComponentCount { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the component id collection. /// Gets or sets the component id collection.
@ -65,24 +89,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public JpegComponent[] Components { get; set; } public JpegComponent[] Components { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum horizontal sampling factor. /// Gets or sets the number of MCU's per line.
/// </summary> /// </summary>
public int MaxHorizontalFactor { get; set; } public int McusPerLine { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum vertical sampling factor. /// Gets or sets the number of MCU's per column.
/// </summary> /// </summary>
public int MaxVerticalFactor { get; set; } public int McusPerColumn { get; set; }
/// <summary> /// <summary>
/// Gets or sets the number of MCU's per line. /// Gets the mcu size of the image.
/// </summary> /// </summary>
public int McusPerLine { get; set; } public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn);
/// <summary> /// <summary>
/// Gets or sets the number of MCU's per column. /// Gets the color depth, in number of bits per pixel.
/// </summary> /// </summary>
public int McusPerColumn { get; set; } public int BitsPerPixel => this.ComponentCount * this.Precision;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
@ -101,15 +125,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary> /// <summary>
/// Allocates the frame component blocks. /// Allocates the frame component blocks.
/// </summary> /// </summary>
public void InitComponents() /// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
public void Init(int maxSubFactorH, int maxSubFactorV)
{ {
this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)this.MaxHorizontalFactor * 8); this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)this.MaxVerticalFactor * 8); this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.ComponentCount; i++) for (int i = 0; i < this.ComponentCount; i++)
{ {
JpegComponent component = this.Components[i]; JpegComponent component = this.Components[i];
component.Init(); component.Init(maxSubFactorH, maxSubFactorV);
} }
} }

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

@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length];
for (int i = 0; i < this.componentProcessors.Length; i++) 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 // single 'stride' rgba32 buffer for conversion between spectral and TPixel

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

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// The only supported precision /// The only supported precision
/// </summary> /// </summary>
private readonly int[] supportedPrecisions = { 8, 12 }; private readonly byte[] supportedPrecisions = { 8, 12 };
/// <summary> /// <summary>
/// The buffer used to temporarily store bytes read from the stream. /// The buffer used to temporarily store bytes read from the stream.
@ -42,21 +42,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private readonly byte[] markerBuffer = new byte[2]; private readonly byte[] markerBuffer = new byte[2];
/// <summary>
/// The DC Huffman tables.
/// </summary>
private HuffmanTable[] dcHuffmanTables;
/// <summary>
/// The AC Huffman tables
/// </summary>
private HuffmanTable[] acHuffmanTables;
/// <summary>
/// The reset interval determined by RST markers.
/// </summary>
private ushort resetInterval;
/// <summary> /// <summary>
/// Whether the image has an EXIF marker. /// Whether the image has an EXIF marker.
/// </summary> /// </summary>
@ -122,30 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegFrame Frame { get; private set; } public JpegFrame Frame { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; } Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize;
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels;
/// <summary>
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary>
public Size ImageSizeInMCU { get; private set; }
/// <summary>
/// Gets the image width
/// </summary>
public int ImageWidth => this.ImageSizeInPixels.Width;
/// <summary>
/// Gets the image height
/// </summary>
public int ImageHeight => this.ImageSizeInPixels.Height;
/// <summary>
/// Gets the color depth, in number of bits per pixel.
/// </summary>
public int BitsPerPixel => this.ComponentCount * this.Frame.Precision;
/// <summary> /// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
@ -157,15 +119,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public ImageMetadata Metadata { get; private set; } public ImageMetadata Metadata { get; private set; }
/// <inheritdoc/>
public int ComponentCount { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public JpegColorSpace ColorSpace { get; private set; } public JpegColorSpace ColorSpace { get; private set; }
/// <inheritdoc/>
public int Precision { get; private set; }
/// <summary> /// <summary>
/// Gets the components. /// Gets the components.
/// </summary> /// </summary>
@ -240,7 +196,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InitIptcProfile(); this.InitIptcProfile();
this.InitDerivedMetadataProperties(); 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);
} }
/// <summary> /// <summary>
@ -270,14 +227,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
this.QuantizationTables = new Block8x8F[4]; 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. // Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695 // https://github.com/SixLabors/ImageSharp/issues/695
while (fileMarker.Marker != JpegConstants.Markers.EOI while (fileMarker.Marker != JpegConstants.Markers.EOI
@ -301,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
if (!metadataOnly) if (!metadataOnly)
{ {
this.ProcessStartOfScanMarker(stream, cancellationToken); this.ProcessStartOfScanMarker(stream, remaining, cancellationToken);
break; break;
} }
else else
@ -392,22 +341,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Set large fields to null. // Set large fields to null.
this.Frame = null; this.Frame = null;
this.dcHuffmanTables = null; this.scanDecoder = null;
this.acHuffmanTables = null;
} }
/// <summary> /// <summary>
/// Returns the correct colorspace based on the image component count /// Returns the correct colorspace based on the image component count
/// </summary> /// </summary>
/// <returns>The <see cref="JpegColorSpace"/></returns> /// <returns>The <see cref="JpegColorSpace"/></returns>
private JpegColorSpace DeduceJpegColorSpace() private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
{ {
if (this.ComponentCount == 1) if (componentCount == 1)
{ {
return JpegColorSpace.Grayscale; return JpegColorSpace.Grayscale;
} }
if (this.ComponentCount == 3) if (componentCount == 3)
{ {
if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{ {
@ -419,14 +367,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return JpegColorSpace.YCbCr; return JpegColorSpace.YCbCr;
} }
if (this.ComponentCount == 4) if (componentCount == 4)
{ {
return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck
? JpegColorSpace.Ycck ? JpegColorSpace.Ycck
: JpegColorSpace.Cmyk; : 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; return default;
} }
@ -565,7 +513,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
} }
var profile = new byte[remaining]; byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining); stream.Read(profile, 0, remaining);
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
@ -599,14 +547,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return; return;
} }
var identifier = new byte[Icclength]; byte[] identifier = new byte[Icclength];
stream.Read(identifier, 0, Icclength); stream.Read(identifier, 0, Icclength);
remaining -= Icclength; // We have read it by this point remaining -= Icclength; // We have read it by this point
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{ {
this.isIcc = true; this.isIcc = true;
var profile = new byte[remaining]; byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining); stream.Read(profile, 0, remaining);
if (this.iccData is null) if (this.iccData is null)
@ -644,7 +592,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker))
{ {
var resourceBlockData = new byte[remaining]; byte[] resourceBlockData = new byte[remaining];
stream.Read(resourceBlockData, 0, remaining); stream.Read(resourceBlockData, 0, remaining);
Span<byte> blockDataSpan = resourceBlockData.AsSpan(); Span<byte> blockDataSpan = resourceBlockData.AsSpan();
@ -659,8 +607,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Span<byte> imageResourceBlockId = blockDataSpan.Slice(0, 2); Span<byte> imageResourceBlockId = blockDataSpan.Slice(0, 2);
if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker))
{ {
var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
int dataStartIdx = 2 + resourceBlockNameLength + 4; int dataStartIdx = 2 + resourceBlockNameLength + 4;
if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize)
{ {
@ -671,8 +619,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
else else
{ {
var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
int dataStartIdx = 2 + resourceBlockNameLength + 4; int dataStartIdx = 2 + resourceBlockNameLength + 4;
if (blockDataSpan.Length < dataStartIdx + resourceDataSize) if (blockDataSpan.Length < dataStartIdx + resourceDataSize)
{ {
@ -695,7 +643,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private static int ReadImageResourceNameLength(Span<byte> blockDataSpan) private static int ReadImageResourceNameLength(Span<byte> blockDataSpan)
{ {
byte nameLength = blockDataSpan[2]; byte nameLength = blockDataSpan[2];
var nameDataSize = nameLength == 0 ? 2 : nameLength; int nameDataSize = nameLength == 0 ? 2 : nameLength;
if (nameDataSize % 2 != 0) if (nameDataSize % 2 != 0)
{ {
nameDataSize++; nameDataSize++;
@ -712,9 +660,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <returns>The block length.</returns> /// <returns>The block length.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int ReadResourceDataLength(Span<byte> blockDataSpan, int resourceBlockNameLength) private static int ReadResourceDataLength(Span<byte> blockDataSpan, int resourceBlockNameLength)
{ => BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
}
/// <summary> /// <summary>
/// Processes the application header containing the Adobe identifier /// Processes the application header containing the Adobe identifier
@ -849,60 +795,62 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
} }
// Read initial marker definitions. // Read initial marker definitions
const int length = 6; const int length = 6;
stream.Read(this.temp, 0, length); stream.Read(this.temp, 0, length);
// We only support 8-bit and 12-bit precision. // 1 byte: Bits/sample precision
if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1) 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."); 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 // 2 byte: Width
{ int frameWidth = (this.temp[3] << 8) | this.temp[4];
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that)
Precision = this.temp[0], if (frameHeight == 0 || frameWidth == 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)
{ {
JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.PixelWidth, this.Frame.PixelHeight); JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight);
} }
this.ImageSizeInPixels = new Size(this.Frame.PixelWidth, this.Frame.PixelHeight); // 1 byte: Number of components
this.ComponentCount = this.Frame.ComponentCount; 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.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
if (!metadataOnly) if (!metadataOnly)
{ {
remaining -= length; remaining -= length;
// Validate: remaining part must be equal to components * 3
const int componentBytes = 3; const int componentBytes = 3;
if (remaining > this.ComponentCount * componentBytes) if (remaining != componentCount * componentBytes)
{ {
JpegThrowHelper.ThrowBadMarker("SOFn", remaining); JpegThrowHelper.ThrowBadMarker("SOFn", remaining);
} }
// components*3 bytes: component data
stream.Read(this.temp, 0, remaining); stream.Read(this.temp, 0, remaining);
// No need to pool this. They max out at 4 // No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[this.ComponentCount]; this.Frame.ComponentIds = new byte[componentCount];
this.Frame.ComponentOrder = new byte[this.ComponentCount]; this.Frame.ComponentOrder = new byte[componentCount];
this.Frame.Components = new JpegComponent[this.ComponentCount]; this.Frame.Components = new JpegComponent[componentCount];
int maxH = 0; int maxH = 0;
int maxV = 0; int maxV = 0;
int index = 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]; byte hv = this.temp[index + 1];
int h = (hv >> 4) & 15; int h = (hv >> 4) & 15;
@ -926,13 +874,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
index += componentBytes; index += componentBytes;
} }
this.Frame.MaxHorizontalFactor = maxH; this.Frame.Init(maxH, maxV);
this.Frame.MaxVerticalFactor = maxV;
this.Frame.InitComponents();
this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn);
// This can be injected in SOF marker callback
this.scanDecoder.InjectFrameData(this.Frame, this); this.scanDecoder.InjectFrameData(this.Frame, this);
} }
} }
@ -996,8 +939,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
i += 17 + codeLengthSum; i += 17 + codeLengthSum;
this.BuildHuffmanTable( this.scanDecoder.BuildHuffmanTable(
tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, tableType,
tableIndex, tableIndex,
codeLengthsSpan, codeLengthsSpan,
huffmanValuesSpan); huffmanValuesSpan);
@ -1020,87 +963,101 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining);
} }
this.resetInterval = this.ReadUint16(stream); this.scanDecoder.ResetInterval = this.ReadUint16(stream);
} }
/// <summary> /// <summary>
/// Processes the SOS (Start of scan marker). /// Processes the SOS (Start of scan marker).
/// </summary> /// </summary>
private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken) private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining, CancellationToken cancellationToken)
{ {
if (this.Frame is null) if (this.Frame is null)
{ {
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
} }
// 1 byte: Number of components in scan
int selectorsCount = stream.ReadByte(); 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; 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; // 1 byte: Component id
int selector = stream.ReadByte(); int componentSelectorId = this.temp[i];
int componentIndex = -1;
for (int j = 0; j < this.Frame.ComponentIds.Length; j++) for (int j = 0; j < this.Frame.ComponentIds.Length; j++)
{ {
byte id = this.Frame.ComponentIds[j]; byte id = this.Frame.ComponentIds[j];
if (selector == id) if (componentSelectorId == id)
{ {
componentIndex = j; componentIndex = j;
break; 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]; this.Frame.ComponentOrder[i / 2] = (byte)componentIndex;
int tableSpec = stream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15;
this.Frame.ComponentOrder[i] = (byte)componentIndex;
}
stream.Read(this.temp, 0, 3); JpegComponent component = this.Frame.Components[componentIndex];
int spectralStart = this.temp[0]; // 1 byte: Huffman table selectors.
int spectralEnd = this.temp[1]; // 4 bits - dc
int successiveApproximation = this.temp[2]; // 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 // Validate: both must be < 4
// Main reason it's not fixed here is to make this commit less intrusive if (dcTableIndex >= 4 || acTableIndex >= 4)
{
// Huffman tables can be calculated directly in the scan decoder class // TODO: extract as separate method?
this.scanDecoder.DcHuffmanTables = this.dcHuffmanTables; JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}");
this.scanDecoder.AcHuffmanTables = this.acHuffmanTables; }
// This can be injectd in DRI marker callback component.DCHuffmanTableId = dcTableIndex;
this.scanDecoder.ResetInterval = this.resetInterval; component.ACHuffmanTableId = acTableIndex;
}
// This can be passed as ParseEntropyCodedData() parameter as it is used only there // 3 bytes: Progressive scan decoding data
this.scanDecoder.ComponentsLength = selectorsCount; 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; this.scanDecoder.SpectralStart = spectralStart;
int spectralEnd = this.temp[1];
this.scanDecoder.SpectralEnd = spectralEnd; this.scanDecoder.SpectralEnd = spectralEnd;
int successiveApproximation = this.temp[2];
this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4;
this.scanDecoder.SuccessiveLow = successiveApproximation & 15; this.scanDecoder.SuccessiveLow = successiveApproximation & 15;
this.scanDecoder.ParseEntropyCodedData(); this.scanDecoder.ParseEntropyCodedData(selectorsCount);
} }
/// <summary>
/// Builds the huffman tables
/// </summary>
/// <param name="tables">The tables</param>
/// <param name="index">The table index</param>
/// <param name="codeLengths">The codelengths</param>
/// <param name="values">The values</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
=> tables[index] = new HuffmanTable(codeLengths, values);
/// <summary> /// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes /// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
/// </summary> /// </summary>

4
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -87,7 +87,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, TestImages.Jpeg.Issues.Fuzz.ArgumentException826B,
TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, TestImages.Jpeg.Issues.Fuzz.ArgumentException826C,
TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, 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<string, float> CustomToleranceValues = private static readonly Dictionary<string, float> CustomToleranceValues =

7
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -62,10 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription); return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription);
} }
public JpegDecoderTests(ITestOutputHelper output) public JpegDecoderTests(ITestOutputHelper output) => this.Output = output;
{
this.Output = output;
}
private ITestOutputHelper Output { get; } private ITestOutputHelper Output { get; }
@ -163,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
var cts = new CancellationTokenSource(); 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); using var pausedStream = new PausedStream(file);
pausedStream.OnWaiting(s => pausedStream.OnWaiting(s =>
{ {

12
tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs

@ -43,12 +43,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400)) 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); 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); var uniform1 = new Size(1, 1);
JpegComponent c0 = decoder.Components[0]; JpegComponent c0 = decoder.Components[0];
@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{ {
sb.AppendLine(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 c0 = decoder.Components[0];
JpegComponent c1 = decoder.Components[1]; JpegComponent c1 = decoder.Components[1];
@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{ {
Assert.Equal(componentCount, decoder.ComponentCount); Assert.Equal(componentCount, decoder.Frame.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length); Assert.Equal(componentCount, decoder.Components.Length);
JpegComponent c0 = decoder.Components[0]; JpegComponent c0 = decoder.Components[0];
@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var uniform1 = new Size(1, 1); var uniform1 = new Size(1, 1);
Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma); Size expectedLumaSizeInBlocks = decoder.Frame.McuSize.MultiplyBy(fLuma);
Size divisor = fLuma.DivideBy(fChroma); Size divisor = fLuma.DivideBy(fChroma);

2
tests/ImageSharp.Tests/TestImages.cs

@ -261,6 +261,8 @@ namespace SixLabors.ImageSharp.Tests
public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg";
public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg"; public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg";
public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.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";
} }
} }

3
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

3
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
Loading…
Cancel
Save