Skip to content

Faster Jpeg Huffman Decoding. #894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7dd4a47
Read from underlying stream less often
JimBobSquarePants Apr 10, 2019
04c3f51
Update benchmark dependencies
JimBobSquarePants Apr 12, 2019
2b59546
Experimental mango port
JimBobSquarePants Apr 18, 2019
015441f
Populate table, 64byte buffer
JimBobSquarePants Apr 18, 2019
0e738bf
Baseline, non RST works
JimBobSquarePants Apr 20, 2019
260738b
15/19 baseline tests pass now.
JimBobSquarePants Apr 20, 2019
ba78663
Optimize position change.
JimBobSquarePants Apr 21, 2019
29ec573
18/19 pass
JimBobSquarePants Apr 21, 2019
bf0fc7e
19/19 baseline decoded
JimBobSquarePants Apr 21, 2019
e78b4bd
Can now decode all images.
JimBobSquarePants Apr 21, 2019
0df60d0
Now faster and much cleaner.
JimBobSquarePants Apr 21, 2019
a1f5b85
Cleanup
JimBobSquarePants Apr 22, 2019
c6334c2
Merge branch 'master' into js/buffered-decode
JimBobSquarePants Apr 22, 2019
0d269f3
Fix reader, update benchmarks
JimBobSquarePants Apr 22, 2019
1f51f55
Merge branch 'master' into js/buffered-decode
JimBobSquarePants Apr 22, 2019
79624dc
Update dependencies
JimBobSquarePants Apr 22, 2019
a8fa85a
Remove unused method
JimBobSquarePants Apr 23, 2019
f15e98e
Merge branch 'master' into js/buffered-decode
JimBobSquarePants Apr 23, 2019
9664adc
No need to clean initial buffer
JimBobSquarePants Apr 23, 2019
5c45fa6
Remove bounds check on ReadByte()
JimBobSquarePants Apr 23, 2019
c19656b
Refactor from feedback
JimBobSquarePants Apr 25, 2019
b97abdc
Merge branch 'master' into js/buffered-decode
JimBobSquarePants Apr 25, 2019
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
2 changes: 1 addition & 1 deletion src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<AdditionalFiles Include="..\..\standards\stylecop.json" />
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta0008" />
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0008" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-beta.61" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-rc.114" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup>
Expand Down
61 changes: 0 additions & 61 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs

This file was deleted.

188 changes: 188 additions & 0 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.IO;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Used to buffer and track the bits read from the Huffman entropy encoded data.
/// </summary>
internal struct HuffmanScanBuffer
{
private readonly DoubleBufferedStreamReader stream;

// The entropy encoded code buffer.
private ulong data;

// The number of valid bits left to read in the buffer.
private int remain;

// Whether there is more data to pull from the stream for the current mcu.
private bool noMore;

public HuffmanScanBuffer(DoubleBufferedStreamReader stream)
{
this.stream = stream;
this.data = 0ul;
this.remain = 0;
this.Marker = JpegConstants.Markers.XFF;
this.MarkerPosition = 0;
this.BadMarker = false;
this.noMore = false;
this.Eof = false;
}

/// <summary>
/// Gets or sets the current, if any, marker in the input stream.
/// </summary>
public byte Marker { get; set; }

/// <summary>
/// Gets or sets the opening position of an identified marker.
/// </summary>
public long MarkerPosition { get; set; }

/// <summary>
/// Gets or sets a value indicating whether we have a bad marker, I.E. One that is not between RST0 and RST7
/// </summary>
public bool BadMarker { get; set; }

/// <summary>
/// Gets or sets a value indicating whether we have prematurely reached the end of the file.
/// </summary>
public bool Eof { get; set; }

[MethodImpl(InliningOptions.ShortMethod)]
public void CheckBits()
{
if (this.remain < 16)
{
this.FillBuffer();
}
}

[MethodImpl(InliningOptions.ShortMethod)]
public void Reset()
{
this.data = 0ul;
this.remain = 0;
this.Marker = JpegConstants.Markers.XFF;
this.MarkerPosition = 0;
this.BadMarker = false;
this.noMore = false;
this.Eof = false;
}

[MethodImpl(InliningOptions.ShortMethod)]
public bool HasRestart()
{
byte m = this.Marker;
return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7;
}

[MethodImpl(InliningOptions.ShortMethod)]
public void FillBuffer()
{
// Attempt to load at least the minimum number of required bits into the buffer.
// We fail to do so only if we hit a marker or reach the end of the input stream.
this.remain += 48;
this.data = (this.data << 48) | this.GetBytes();
}

[MethodImpl(InliningOptions.ShortMethod)]
public unsafe int DecodeHuffman(ref HuffmanTable h)
{
this.CheckBits();
int v = this.PeekBits(JpegConstants.Huffman.LookupBits);
int symbol = h.LookaheadValue[v];
int size = h.LookaheadSize[v];

if (size == JpegConstants.Huffman.SlowBits)
{
ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remain);
while (x > h.MaxCode[size])
{
size++;
}

v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size));
symbol = h.Values[h.ValOffset[size] + v];
}

this.remain -= size;

return symbol;
}

[MethodImpl(InliningOptions.ShortMethod)]
public int Receive(int nbits)
{
this.CheckBits();
return Extend(this.GetBits(nbits), nbits);
}

[MethodImpl(InliningOptions.ShortMethod)]
public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remain -= nbits, nbits);

[MethodImpl(InliningOptions.ShortMethod)]
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remain - nbits, nbits);

[MethodImpl(InliningOptions.ShortMethod)]
private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);

[MethodImpl(InliningOptions.ShortMethod)]
private static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1));

[MethodImpl(InliningOptions.ShortMethod)]
private ulong GetBytes()
{
ulong temp = 0;
for (int i = 0; i < 6; i++)
{
int b = this.noMore ? 0 : this.stream.ReadByte();

if (b == -1)
{
// We've encountered the end of the file stream which means there's no EOI marker in the image
// or the SOS marker has the wrong dimensions set.
this.Eof = true;
b = 0;
}

// Found a marker.
if (b == JpegConstants.Markers.XFF)
{
this.MarkerPosition = this.stream.Position - 1;
int c = this.stream.ReadByte();
while (c == JpegConstants.Markers.XFF)
{
c = this.stream.ReadByte();

if (c == -1)
{
this.Eof = true;
c = 0;
break;
}
}

if (c != 0)
{
this.Marker = (byte)c;
this.noMore = true;
if (!this.HasRestart())
{
this.BadMarker = true;
}
}
}

temp = (temp << 8) | (ulong)(long)b;
}

return temp;
}
}
}
Loading