@@ -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<<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<<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