Browse Source

Merge branch 'master' into fixdsread

pull/1707/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
defec9cdba
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;
// Frame related
/// <summary>
/// <see cref="JpegFrame"/> instance containing decoding-related information.
/// </summary>
private JpegFrame frame;
/// <summary>
/// Shortcut for <see cref="frame"/>.Components.
/// </summary>
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;
// 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.
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.
private ZigZag dctZigZag;
@ -55,14 +78,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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
/// <summary>
/// Sets reset interval determined by RST markers.
/// </summary>
public int ResetInterval
{
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.
public int SpectralStart { get; set; }
@ -90,10 +112,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Decodes the entropy coded data.
/// </summary>
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineData()
{
if (this.ComponentsLength == this.frame.ComponentCount)
if (this.componentsCount == this.frame.ComponentCount)
{
this.ParseBaselineDataInterleaved();
}
@ -143,13 +167,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
// AC scans may have only one component.
if (this.ComponentsLength != 1)
if (this.componentsCount != 1)
{
invalid = true;
}
@ -304,7 +328,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.CheckProgressiveData();
if (this.ComponentsLength == 1)
if (this.componentsCount == 1)
{
this.ParseProgressiveDataNonInterleaved();
}
@ -323,11 +347,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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>
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>
/// Gets the color space
/// </summary>
JpegColorSpace ColorSpace { get; }
/// <summary>
/// Gets the number of bits used for precision.
/// </summary>
int Precision { get; }
/// <summary>
/// Gets the components.
/// </summary>
@ -41,4 +26,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
Block8x8F[] QuantizationTables { get; }
}
}
}

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

@ -38,11 +38,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private Size subSamplingDivisors;
/// <summary>
/// Defines the maximum value derived from the bitdepth.
/// </summary>
private readonly int maximumValue;
/// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary>
@ -53,7 +48,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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;

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

@ -106,20 +106,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
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(
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)
{

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

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

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

@ -10,15 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
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>
/// 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>
public bool Extended { get; set; }
public bool Extended { get; private set; }
/// <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>
public bool Progressive { get; set; }
public bool Progressive { get; private set; }
/// <summary>
/// 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; }
/// <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>
public byte Precision { get; set; }
public int PixelHeight { get; private set; }
/// <summary>
/// Gets or sets the number of scanlines within the frame.
/// Gets the number of pixels per line.
/// </summary>
public int PixelHeight { get; set; }
public int PixelWidth { get; private set; }
/// <summary>
/// Gets or sets the number of samples per scanline.
/// Gets the pixel size of the image.
/// </summary>
public int PixelWidth { get; set; }
public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight);
/// <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>
public byte ComponentCount { get; set; }
public byte ComponentCount { get; private set; }
/// <summary>
/// Gets or sets the component id collection.
@ -65,24 +89,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public JpegComponent[] Components { get; set; }
/// <summary>
/// Gets or sets the maximum horizontal sampling factor.
/// Gets or sets the number of MCU's per line.
/// </summary>
public int MaxHorizontalFactor { get; set; }
public int McusPerLine { get; set; }
/// <summary>
/// Gets or sets the maximum vertical sampling factor.
/// Gets or sets the number of MCU's per column.
/// </summary>
public int MaxVerticalFactor { get; set; }
public int McusPerColumn { get; set; }
/// <summary>
/// Gets or sets the number of MCU's per line.
/// Gets the mcu size of the image.
/// </summary>
public int McusPerLine { get; set; }
public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn);
/// <summary>
/// Gets or sets the number of MCU's per column.
/// Gets the color depth, in number of bits per pixel.
/// </summary>
public int McusPerColumn { get; set; }
public int BitsPerPixel => this.ComponentCount * this.Precision;
/// <inheritdoc/>
public void Dispose()
@ -101,15 +125,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Allocates the frame component blocks.
/// </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.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);
}
}

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];
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

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

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// The only supported precision
/// </summary>
private readonly int[] supportedPrecisions = { 8, 12 };
private readonly byte[] supportedPrecisions = { 8, 12 };
/// <summary>
/// The buffer used to temporarily store bytes read from the stream.
@ -42,21 +42,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
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>
/// Whether the image has an EXIF marker.
/// </summary>
@ -122,30 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegFrame Frame { get; private set; }
/// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; }
/// <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;
Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize;
/// <summary>
/// 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>
public ImageMetadata Metadata { get; private set; }
/// <inheritdoc/>
public int ComponentCount { get; private set; }
/// <inheritdoc/>
public JpegColorSpace ColorSpace { get; private set; }
/// <inheritdoc/>
public int Precision { get; private set; }
/// <summary>
/// Gets the components.
/// </summary>
@ -240,7 +196,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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);
}
/// <summary>
@ -270,14 +227,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker(stream, cancellationToken);
this.ProcessStartOfScanMarker(stream, remaining, cancellationToken);
break;
}
else
@ -392,22 +341,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Set large fields to null.
this.Frame = null;
this.dcHuffmanTables = null;
this.acHuffmanTables = null;
this.scanDecoder = null;
}
/// <summary>
/// Returns the correct colorspace based on the image component count
/// </summary>
/// <returns>The <see cref="JpegColorSpace"/></returns>
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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<byte> blockDataSpan = resourceBlockData.AsSpan();
@ -659,8 +607,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Span<byte> 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private static int ReadImageResourceNameLength(Span<byte> 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <returns>The block length.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static int ReadResourceDataLength(Span<byte> blockDataSpan, int resourceBlockNameLength)
{
return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
}
=> BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
/// <summary>
/// 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.");
}
// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
i += 17 + codeLengthSum;
this.BuildHuffmanTable(
tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables,
this.scanDecoder.BuildHuffmanTable(
tableType,
tableIndex,
codeLengthsSpan,
huffmanValuesSpan);
@ -1020,87 +963,101 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining);
}
this.resetInterval = this.ReadUint16(stream);
this.scanDecoder.ResetInterval = this.ReadUint16(stream);
}
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
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);
}
/// <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>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
/// </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.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<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);
}
public JpegDecoderTests(ITestOutputHelper output)
{
this.Output = output;
}
public JpegDecoderTests(ITestOutputHelper output) => this.Output = output;
private ITestOutputHelper Output { get; }
@ -163,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
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 =>
{

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))
{
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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var uniform1 = new Size(1, 1);
Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma);
Size expectedLumaSizeInBlocks = decoder.Frame.McuSize.MultiplyBy(fLuma);
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 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";
}
}

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