Skip to content

Commit 2fcba54

Browse files
Faster Jpeg Huffman Decoding. (#894)
* Read from underlying stream less often * Update benchmark dependencies * Experimental mango port Currently broken * Populate table, 64byte buffer Still broken. * Baseline, non RST works * 15/19 baseline tests pass now. * Optimize position change. * 18/19 pass * 19/19 baseline decoded * Can now decode all images. * Now faster and much cleaner. * Cleanup * Fix reader, update benchmarks * Update dependencies * Remove unused method * No need to clean initial buffer * Remove bounds check on ReadByte() * Refactor from feedback
1 parent 0a78af5 commit 2fcba54

17 files changed

+1160
-1270
lines changed

src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
<AdditionalFiles Include="..\..\standards\stylecop.json" />
4040
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta0008" />
4141
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0008" />
42-
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-beta.61" PrivateAssets="All" />
42+
<PackageReference Include="StyleCop.Analyzers" Version="1.1.1-rc.114" PrivateAssets="All" />
4343
</ItemGroup>
4444

4545
<PropertyGroup>

src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Runtime.CompilerServices;
5+
using SixLabors.ImageSharp.IO;
6+
7+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
8+
{
9+
/// <summary>
10+
/// Used to buffer and track the bits read from the Huffman entropy encoded data.
11+
/// </summary>
12+
internal struct HuffmanScanBuffer
13+
{
14+
private readonly DoubleBufferedStreamReader stream;
15+
16+
// The entropy encoded code buffer.
17+
private ulong data;
18+
19+
// The number of valid bits left to read in the buffer.
20+
private int remain;
21+
22+
// Whether there is more data to pull from the stream for the current mcu.
23+
private bool noMore;
24+
25+
public HuffmanScanBuffer(DoubleBufferedStreamReader stream)
26+
{
27+
this.stream = stream;
28+
this.data = 0ul;
29+
this.remain = 0;
30+
this.Marker = JpegConstants.Markers.XFF;
31+
this.MarkerPosition = 0;
32+
this.BadMarker = false;
33+
this.noMore = false;
34+
this.Eof = false;
35+
}
36+
37+
/// <summary>
38+
/// Gets or sets the current, if any, marker in the input stream.
39+
/// </summary>
40+
public byte Marker { get; set; }
41+
42+
/// <summary>
43+
/// Gets or sets the opening position of an identified marker.
44+
/// </summary>
45+
public long MarkerPosition { get; set; }
46+
47+
/// <summary>
48+
/// Gets or sets a value indicating whether we have a bad marker, I.E. One that is not between RST0 and RST7
49+
/// </summary>
50+
public bool BadMarker { get; set; }
51+
52+
/// <summary>
53+
/// Gets or sets a value indicating whether we have prematurely reached the end of the file.
54+
/// </summary>
55+
public bool Eof { get; set; }
56+
57+
[MethodImpl(InliningOptions.ShortMethod)]
58+
public void CheckBits()
59+
{
60+
if (this.remain < 16)
61+
{
62+
this.FillBuffer();
63+
}
64+
}
65+
66+
[MethodImpl(InliningOptions.ShortMethod)]
67+
public void Reset()
68+
{
69+
this.data = 0ul;
70+
this.remain = 0;
71+
this.Marker = JpegConstants.Markers.XFF;
72+
this.MarkerPosition = 0;
73+
this.BadMarker = false;
74+
this.noMore = false;
75+
this.Eof = false;
76+
}
77+
78+
[MethodImpl(InliningOptions.ShortMethod)]
79+
public bool HasRestart()
80+
{
81+
byte m = this.Marker;
82+
return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7;
83+
}
84+
85+
[MethodImpl(InliningOptions.ShortMethod)]
86+
public void FillBuffer()
87+
{
88+
// Attempt to load at least the minimum number of required bits into the buffer.
89+
// We fail to do so only if we hit a marker or reach the end of the input stream.
90+
this.remain += 48;
91+
this.data = (this.data << 48) | this.GetBytes();
92+
}
93+
94+
[MethodImpl(InliningOptions.ShortMethod)]
95+
public unsafe int DecodeHuffman(ref HuffmanTable h)
96+
{
97+
this.CheckBits();
98+
int v = this.PeekBits(JpegConstants.Huffman.LookupBits);
99+
int symbol = h.LookaheadValue[v];
100+
int size = h.LookaheadSize[v];
101+
102+
if (size == JpegConstants.Huffman.SlowBits)
103+
{
104+
ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remain);
105+
while (x > h.MaxCode[size])
106+
{
107+
size++;
108+
}
109+
110+
v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size));
111+
symbol = h.Values[h.ValOffset[size] + v];
112+
}
113+
114+
this.remain -= size;
115+
116+
return symbol;
117+
}
118+
119+
[MethodImpl(InliningOptions.ShortMethod)]
120+
public int Receive(int nbits)
121+
{
122+
this.CheckBits();
123+
return Extend(this.GetBits(nbits), nbits);
124+
}
125+
126+
[MethodImpl(InliningOptions.ShortMethod)]
127+
public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remain -= nbits, nbits);
128+
129+
[MethodImpl(InliningOptions.ShortMethod)]
130+
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remain - nbits, nbits);
131+
132+
[MethodImpl(InliningOptions.ShortMethod)]
133+
private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);
134+
135+
[MethodImpl(InliningOptions.ShortMethod)]
136+
private static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1));
137+
138+
[MethodImpl(InliningOptions.ShortMethod)]
139+
private ulong GetBytes()
140+
{
141+
ulong temp = 0;
142+
for (int i = 0; i < 6; i++)
143+
{
144+
int b = this.noMore ? 0 : this.stream.ReadByte();
145+
146+
if (b == -1)
147+
{
148+
// We've encountered the end of the file stream which means there's no EOI marker in the image
149+
// or the SOS marker has the wrong dimensions set.
150+
this.Eof = true;
151+
b = 0;
152+
}
153+
154+
// Found a marker.
155+
if (b == JpegConstants.Markers.XFF)
156+
{
157+
this.MarkerPosition = this.stream.Position - 1;
158+
int c = this.stream.ReadByte();
159+
while (c == JpegConstants.Markers.XFF)
160+
{
161+
c = this.stream.ReadByte();
162+
163+
if (c == -1)
164+
{
165+
this.Eof = true;
166+
c = 0;
167+
break;
168+
}
169+
}
170+
171+
if (c != 0)
172+
{
173+
this.Marker = (byte)c;
174+
this.noMore = true;
175+
if (!this.HasRestart())
176+
{
177+
this.BadMarker = true;
178+
}
179+
}
180+
}
181+
182+
temp = (temp << 8) | (ulong)(long)b;
183+
}
184+
185+
return temp;
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)