Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6f45485
Huffman tables are now handled by scan decoder, not decoder core
Jul 15, 2021
d9745e4
Restart interval is now handled by scan decoder
Jul 15, 2021
b299e1a
Scan component count refactor
Jul 15, 2021
9067c64
Added SOF data precision comments, decoupled precision value from dec…
Jul 15, 2021
04eef15
Added frame dimensions proper check & comments
Jul 15, 2021
24b1ca6
Added component count proper check, comments & decoupled it from actu…
Jul 15, 2021
6b214ca
Removed core obsolete properties, decoupled frame metadata from the d…
Jul 16, 2021
7077473
Decoupled mcu size from the decoder, fixed SOF component bytes length…
Jul 16, 2021
4d599d1
Comments, docs, decoupling, removed redundant properties
Jul 16, 2021
c751b27
Removed first component dependency in compoentn initialization code
Jul 16, 2021
0e4e950
Introduced JpegFrame ctor, closed some setters
Jul 16, 2021
36a1ea6
Color channel max value is now cached per jpeg frame
Jul 16, 2021
00c1a21
Fixed fuzzed issue related to selectorsCount, added appropriate checks
Jul 16, 2021
245b786
Small refactoring, added progressive data comments
Jul 16, 2021
5596ce1
Refactored componentIndex validation
Jul 16, 2021
95bec1c
Added comments, validated huffman table indices
Jul 16, 2021
0ace1a0
Added issue-1693 images & tests cases - all passing after fix
Jul 16, 2021
a92161f
Fixed some warnings
Jul 16, 2021
7c8261a
Reduced number of stream reads in SOS marker, added check for remaini…
Jul 16, 2021
db04e41
Added comments to major properties
Jul 16, 2021
9750014
Added todo
Jul 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,24 @@ internal class HuffmanScanDecoder
{
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.
Expand All @@ -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;

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

Expand All @@ -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
/// <summary>
/// Sets reset interval determined by RST markers.
/// </summary>
public int ResetInterval
{
set
Expand All @@ -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; }

Expand All @@ -90,10 +112,12 @@ public int ResetInterval
/// <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;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
}
Expand All @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -272,7 +296,7 @@ private void CheckProgressiveData()
}

// AC scans may have only one component.
if (this.ComponentsLength != 1)
if (this.componentsCount != 1)
{
invalid = true;
}
Expand Down Expand Up @@ -304,7 +328,7 @@ private void ParseProgressiveData()
{
this.CheckProgressiveData();

if (this.ComponentsLength == 1)
if (this.componentsCount == 1)
{
this.ParseProgressiveDataNonInterleaved();
}
Expand All @@ -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();
}

Expand All @@ -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;
Expand Down Expand Up @@ -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++)
Expand Down Expand Up @@ -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++)
Expand Down Expand Up @@ -722,5 +746,19 @@ private bool HandleRestart()

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 changes: 1 addition & 16 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -41,4 +26,4 @@ internal interface IRawJpegData : IDisposable
/// </summary>
Block8x8F[] QuantizationTables { get; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ internal struct JpegBlockPostProcessor
/// </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>
Expand All @@ -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;
Expand Down
14 changes: 9 additions & 5 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,24 @@ public void Dispose()
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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ internal class JpegComponentPostProcessor : IDisposable
/// </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;
Expand Down Expand Up @@ -70,7 +77,8 @@ public void CopyBlocksToColorBuffer(int step)
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;

Expand Down
Loading