Skip to content

Commit 5eb0122

Browse files
tannergoodingJimBobSquarePants
authored andcommitted
Updating some readonly static data in JpegEncoderCore to take advantage of compiler functionality. (#855)
1 parent 47e8f2c commit 5eb0122

File tree

2 files changed

+124
-82
lines changed

2 files changed

+124
-82
lines changed

src/ImageSharp/Common/Extensions/StreamExtensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Buffers;
56
using System.IO;
67

78
using SixLabors.ImageSharp.Memory;
@@ -82,5 +83,25 @@ public static void Write(this Stream stream, IManagedByteBuffer buffer)
8283
{
8384
stream.Write(buffer.Array, 0, buffer.Length());
8485
}
86+
87+
#if NET472 || NETSTANDARD1_3 || NETSTANDARD2_0
88+
// This is a port of the CoreFX implementation and is MIT Licensed: https://github.com/dotnet/coreclr/blob/c4dca1072d15bdda64c754ad1ea474b1580fa554/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L770
89+
public static void Write(this Stream stream, ReadOnlySpan<byte> buffer)
90+
{
91+
// This uses ArrayPool<byte>.Shared, rather than taking a MemoryAllocator,
92+
// in order to match the signature of the framework method that exists in
93+
// .NET Core.
94+
byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length);
95+
try
96+
{
97+
buffer.CopyTo(sharedBuffer);
98+
stream.Write(sharedBuffer, 0, buffer.Length);
99+
}
100+
finally
101+
{
102+
ArrayPool<byte>.Shared.Return(sharedBuffer);
103+
}
104+
}
105+
#endif
85106
}
86107
}

src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

Lines changed: 103 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -27,85 +27,6 @@ internal sealed unsafe class JpegEncoderCore
2727
/// </summary>
2828
private const int QuantizationTableCount = 2;
2929

30-
/// <summary>
31-
/// Counts the number of bits needed to hold an integer.
32-
/// </summary>
33-
private static readonly uint[] BitCountLut =
34-
{
35-
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5,
36-
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
37-
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
38-
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
39-
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
40-
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
41-
7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
42-
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
43-
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
44-
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
45-
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
46-
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
47-
8, 8, 8,
48-
};
49-
50-
/// <summary>
51-
/// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
52-
/// - the marker length "\x00\x0c",
53-
/// - the number of components "\x03",
54-
/// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
55-
/// - component 2 uses DC table 1 and AC table 1 "\x02\x11",
56-
/// - component 3 uses DC table 1 and AC table 1 "\x03\x11",
57-
/// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
58-
/// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
59-
/// should be 0x00, 0x3f, 0x00&lt;&lt;4 | 0x00.
60-
/// </summary>
61-
private static readonly byte[] SosHeaderYCbCr =
62-
{
63-
JpegConstants.Markers.XFF, JpegConstants.Markers.SOS,
64-
65-
// Marker
66-
0x00, 0x0c,
67-
68-
// Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
69-
0x03, // Number of components in a scan, 3
70-
0x01, // Component Id Y
71-
0x00, // DC/AC Huffman table
72-
0x02, // Component Id Cb
73-
0x11, // DC/AC Huffman table
74-
0x03, // Component Id Cr
75-
0x11, // DC/AC Huffman table
76-
0x00, // Ss - Start of spectral selection.
77-
0x3f, // Se - End of spectral selection.
78-
0x00
79-
80-
// Ah + Ah (Successive approximation bit position high + low)
81-
};
82-
83-
/// <summary>
84-
/// The unscaled quantization tables in zig-zag order. Each
85-
/// encoder copies and scales the tables according to its quality parameter.
86-
/// The values are derived from section K.1 after converting from natural to
87-
/// zig-zag order.
88-
/// </summary>
89-
private static readonly byte[,] UnscaledQuant =
90-
{
91-
{
92-
// Luminance.
93-
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
94-
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
95-
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
96-
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
97-
100, 120, 92, 101, 103, 99,
98-
},
99-
{
100-
// Chrominance.
101-
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
102-
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
103-
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
104-
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
105-
99, 99, 99, 99, 99, 99, 99, 99,
106-
}
107-
};
108-
10930
/// <summary>
11031
/// A scratch buffer to reduce allocations.
11132
/// </summary>
@@ -167,6 +88,103 @@ public JpegEncoderCore(IJpegEncoderOptions options)
16788
this.subsample = options.Subsample;
16889
}
16990

91+
/// <summary>
92+
/// Gets the counts the number of bits needed to hold an integer.
93+
/// </summary>
94+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
95+
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
96+
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
97+
private static ReadOnlySpan<byte> BitCountLut => new byte[]
98+
{
99+
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5,
100+
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
101+
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
102+
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
103+
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
104+
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
105+
7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
106+
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
107+
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
108+
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
109+
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
110+
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
111+
8, 8, 8,
112+
};
113+
114+
/// <summary>
115+
/// Gets the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
116+
/// - the marker length "\x00\x0c",
117+
/// - the number of components "\x03",
118+
/// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
119+
/// - component 2 uses DC table 1 and AC table 1 "\x02\x11",
120+
/// - component 3 uses DC table 1 and AC table 1 "\x03\x11",
121+
/// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
122+
/// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
123+
/// should be 0x00, 0x3f, 0x00&lt;&lt;4 | 0x00.
124+
/// </summary>
125+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
126+
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
127+
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
128+
private static ReadOnlySpan<byte> SosHeaderYCbCr => new byte[]
129+
{
130+
JpegConstants.Markers.XFF, JpegConstants.Markers.SOS,
131+
132+
// Marker
133+
0x00, 0x0c,
134+
135+
// Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
136+
0x03, // Number of components in a scan, 3
137+
0x01, // Component Id Y
138+
0x00, // DC/AC Huffman table
139+
0x02, // Component Id Cb
140+
0x11, // DC/AC Huffman table
141+
0x03, // Component Id Cr
142+
0x11, // DC/AC Huffman table
143+
0x00, // Ss - Start of spectral selection.
144+
0x3f, // Se - End of spectral selection.
145+
0x00
146+
147+
// Ah + Ah (Successive approximation bit position high + low)
148+
};
149+
150+
/// <summary>
151+
/// Gets the unscaled quantization tables in zig-zag order. Each
152+
/// encoder copies and scales the tables according to its quality parameter.
153+
/// The values are derived from section K.1 after converting from natural to
154+
/// zig-zag order.
155+
/// </summary>
156+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
157+
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
158+
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
159+
private static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[]
160+
{
161+
// Luminance.
162+
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
163+
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
164+
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
165+
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
166+
100, 120, 92, 101, 103, 99,
167+
};
168+
169+
/// <summary>
170+
/// Gets the unscaled quantization tables in zig-zag order. Each
171+
/// encoder copies and scales the tables according to its quality parameter.
172+
/// The values are derived from section K.1 after converting from natural to
173+
/// zig-zag order.
174+
/// </summary>
175+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
176+
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
177+
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
178+
private static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[]
179+
{
180+
// Chrominance.
181+
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
182+
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
183+
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
184+
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
185+
99, 99, 99, 99, 99, 99, 99, 99,
186+
};
187+
170188
/// <summary>
171189
/// Encode writes the image to the jpeg baseline format with the given options.
172190
/// </summary>
@@ -259,9 +277,12 @@ private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref
259277
/// <param name="quant">The quantization table.</param>
260278
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
261279
{
280+
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
281+
var unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance;
282+
262283
for (int j = 0; j < Block8x8F.Size; j++)
263284
{
264-
int x = UnscaledQuant[i, j];
285+
int x = unscaledQuant[j];
265286
x = ((x * scale) + 50) / 100;
266287
if (x < 1)
267288
{
@@ -357,7 +378,7 @@ private void EmitHuffRLE(HuffIndex index, int runLength, int value)
357378
}
358379
else
359380
{
360-
bt = 8 + BitCountLut[a >> 8];
381+
bt = 8 + (uint)BitCountLut[a >> 8];
361382
}
362383

363384
this.EmitHuff(index, (int)((uint)(runLength << 4) | bt));
@@ -871,7 +892,7 @@ private void WriteStartOfScan<TPixel>(Image<TPixel> image)
871892
{
872893
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
873894
// TODO: We should allow grayscale writing.
874-
this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length);
895+
this.outputStream.Write(SosHeaderYCbCr);
875896

876897
switch (this.subsample)
877898
{

0 commit comments

Comments
 (0)