77
88using System ;
99using System . Collections . Generic ;
10+ using System . Diagnostics ;
1011using System . Diagnostics . CodeAnalysis ;
1112
1213namespace Net . Codecrete . QrCodeGenerator
@@ -43,97 +44,114 @@ internal static List<DataSegment> BuildSegments(ArraySegment<byte> bytes, int ve
4344 // The algorithm first determines the best encoding mode for each byte
4445 // and builds blocks of bytes with the same encoding mode.
4546 var blocks = BuildBlocks ( bytes , considerKanjiMode ) ;
47+ var blockCount = blocks . Length ;
4648
4749 // Since switching from one mode to another and back requires additional bits,
4850 // the additional cost of switching can be higher than the savings
4951 // from using a more efficient mode. If this is the case, two or three blocks are merged.
5052 // In the first step, short numeric blocks are merged with alphanumeric blocks.
5153 // In the second step, all types of blocks are merged into byte blocks.
52- MergeBlocks ( blocks , version , DataSegmentMode . Alphanumeric ,
54+ blockCount = MergeBlocks ( blocks , blockCount , version , DataSegmentMode . Alphanumeric ,
5355 ( mode0 , mode1 , mode2 ) => mode0 == DataSegmentMode . Alphanumeric
5456 && mode1 == DataSegmentMode . Numeric && mode2 == mode0 ,
5557 ( mode0 , mode1 ) => ( mode0 == DataSegmentMode . Alphanumeric && mode1 == DataSegmentMode . Numeric )
5658 || ( mode0 == DataSegmentMode . Numeric && mode1 == DataSegmentMode . Alphanumeric )
5759 ) ;
58- MergeBlocks ( blocks , version , DataSegmentMode . Binary ,
60+ blockCount = MergeBlocks ( blocks , blockCount , version , DataSegmentMode . Binary ,
5961 ( mode0 , mode1 , mode2 ) => mode1 != DataSegmentMode . Binary && mode2 == mode0 ,
6062 ( mode0 , mode1 ) => ( mode0 == DataSegmentMode . Binary && mode1 != DataSegmentMode . Binary )
6163 || ( mode0 != DataSegmentMode . Binary && mode1 == DataSegmentMode . Binary )
6264 ) ;
6365
66+ var segments = new List < DataSegment > ( blockCount ) ;
6467 var offset = 0 ;
65- return blocks . ConvertAll ( block =>
68+ for ( var i = 0 ; i < blockCount ; i += 1 )
6669 {
70+ var block = blocks [ i ] ;
6771 var blockBytes = bytes . MakeSlice ( offset , block . Length ) ;
6872 offset += block . Length ;
69- return DataSegment . MakeSegment ( block . Mode , blockBytes ) ;
70- } ) ;
73+ segments . Add ( DataSegment . MakeSegment ( block . Mode , blockBytes ) ) ;
74+ }
75+
76+ return segments ;
7177 }
7278
7379 /// <summary>
7480 /// Merges blocks if the length can be reduced.
7581 /// </summary>
76- /// <param name="blocks">The list of blocks to process.</param>
82+ /// <param name="blocks">The array of blocks to process.</param>
83+ /// <param name="blockCount">The number of active blocks in the array.</param>
7784 /// <param name="version">The QR code version.</param>
7885 /// <param name="mergedMode">The data segment mode of the merged block.</param>
7986 /// <param name="merge3Condition">Condition for testing 3 consecutive blocks.</param>
8087 /// <param name="merge2Condition">Condition for testing 2 consecutive blocks.</param>
88+ /// <returns>Number of remaining blocks in array</returns>
8189 [ SuppressMessage ( "csharpsquid" , "S3776" ) ]
82- private static void MergeBlocks ( List < Block > blocks , int version , DataSegmentMode mergedMode ,
90+ private static int MergeBlocks ( Block [ ] blocks , int blockCount , int version , DataSegmentMode mergedMode ,
8391 Func < DataSegmentMode , DataSegmentMode , DataSegmentMode , bool > merge3Condition ,
8492 Func < DataSegmentMode , DataSegmentMode , bool > merge2Condition )
8593 {
8694 var previousCount = - 1 ;
87- while ( blocks . Count > 1 && previousCount != blocks . Count )
95+ while ( blockCount > 1 && previousCount != blockCount )
8896 {
89- previousCount = blocks . Count ;
90-
91- // Work back to front to reduce the amount of copying in `blocks`
92- var index = blocks . Count - 1 ;
93- while ( index > 0 )
97+ previousCount = blockCount ;
98+
99+ var targetIndex = 1 ;
100+ var sourceIndex = 1 ;
101+ while ( sourceIndex < blockCount )
94102 {
95- var mode0 = blocks [ index ] . Mode ;
96- var mode1 = blocks [ index - 1 ] . Mode ;
97- DataSegmentMode ? mode2 = index >= 2 ? blocks [ index - 2 ] . Mode : ( DataSegmentMode ? ) null ;
103+ var merged = false ;
104+ var mode0 = blocks [ targetIndex - 1 ] . Mode ;
105+ var mode1 = blocks [ sourceIndex ] . Mode ;
98106
99- // Case 1: merge 3 blocks
107+ // Case 1: merge 3 blocks (last processed one plus 2 unprocessed ones)
100108 // Test if the bit stream is shorter if all 3 blocks are merged (using the specified merged mode).
101- if ( mode2 != null && merge3Condition ( mode0 , mode1 , mode2 . Value ) )
109+ if ( sourceIndex + 1 < blockCount && merge3Condition ( mode0 , mode1 , blocks [ sourceIndex + 1 ] . Mode ) )
102110 {
103111 var mergedPayloadLength =
104- blocks [ index - 2 ] . Length + blocks [ index - 1 ] . Length + blocks [ index ] . Length ;
112+ blocks [ targetIndex - 1 ] . Length + blocks [ sourceIndex ] . Length + blocks [ sourceIndex + 1 ] . Length ;
105113 var mergedBlock = new Block { Mode = mergedMode , Length = mergedPayloadLength } ;
106114 var mergedLength = mergedBlock . GetSegmentLength ( version ) ;
107- var separateLength = blocks [ index - 2 ] . GetSegmentLength ( version )
108- + blocks [ index - 1 ] . GetSegmentLength ( version )
109- + blocks [ index ] . GetSegmentLength ( version ) ;
115+ var separateLength = blocks [ targetIndex - 1 ] . GetSegmentLength ( version )
116+ + blocks [ sourceIndex ] . GetSegmentLength ( version )
117+ + blocks [ sourceIndex + 1 ] . GetSegmentLength ( version ) ;
110118 if ( mergedLength <= separateLength )
111119 {
112- blocks [ index - 2 ] = mergedBlock ;
113- blocks . RemoveRange ( index - 1 , 2 ) ;
114- index -= 1 ;
120+ blocks [ targetIndex - 1 ] = mergedBlock ;
121+ sourceIndex += 2 ;
122+ merged = true ;
115123 }
116124 }
117125
118- // Case 2: merge 2 blocks
126+ // Case 2: merge 2 blocks (last processed one and the current unprocessed one)
119127 // Test if the bit stream is shorter if the 2 blocks are merged (using the specified merged mode).
120128 else if ( merge2Condition ( mode0 , mode1 ) )
121129 {
122- var mergedBlock = new Block
123- { Mode = mergedMode , Length = blocks [ index - 1 ] . Length + blocks [ index ] . Length } ;
130+ var mergedPayloadLength = blocks [ targetIndex - 1 ] . Length + blocks [ sourceIndex ] . Length ;
131+ var mergedBlock = new Block { Mode = mergedMode , Length = mergedPayloadLength } ;
124132 var mergedLength = mergedBlock . GetSegmentLength ( version ) ;
125- var separateLength = blocks [ index - 1 ] . GetSegmentLength ( version ) +
126- blocks [ index ] . GetSegmentLength ( version ) ;
133+ var separateLength = blocks [ targetIndex - 1 ] . GetSegmentLength ( version ) +
134+ blocks [ sourceIndex ] . GetSegmentLength ( version ) ;
127135 if ( mergedLength <= separateLength )
128136 {
129- blocks [ index - 1 ] = mergedBlock ;
130- blocks . RemoveAt ( index ) ;
137+ blocks [ targetIndex - 1 ] = mergedBlock ;
138+ sourceIndex += 1 ;
139+ merged = true ;
131140 }
132141 }
133-
134- index -= 1 ;
142+
143+ if ( ! merged )
144+ {
145+ blocks [ targetIndex ] = blocks [ sourceIndex ] ;
146+ targetIndex += 1 ;
147+ sourceIndex += 1 ;
148+ }
135149 }
150+
151+ blockCount = targetIndex ;
136152 }
153+
154+ return blockCount ;
137155 }
138156
139157 /// <summary>
@@ -146,17 +164,19 @@ private static void MergeBlocks(List<Block> blocks, int version, DataSegmentMode
146164 /// <param name="bytes">Bytes to process</param>
147165 /// <param name="useKanji">If <c>true</c>, Kanji encoding is considered; if <c>false</c>, Kanji encoding is not used.</param>
148166 /// <returns>List of blocks</returns>
149- private static List < Block > BuildBlocks ( ArraySegment < byte > bytes , bool useKanji )
167+ private static Block [ ] BuildBlocks ( ArraySegment < byte > bytes , bool useKanji )
150168 {
151169 if ( bytes . Count == 0 )
152170 {
153- return new List < Block > ( ) ;
171+ return Array . Empty < Block > ( ) ;
154172 }
155173
156174 var modes = CalcCompactionMode ( bytes , useKanji ) ;
157-
175+
158176 // create blocks
159- var blockList = new List < Block > ( ) ;
177+ var modeChanges = CountModeChanges ( modes ) ;
178+ var blocks = new Block [ modeChanges ] ;
179+ var blockCount = 0 ;
160180 var blockStartIndex = 0 ;
161181 var previousMode = modes [ 0 ] ;
162182 for ( var i = 0 ; i < modes . Length ; i += 1 )
@@ -167,14 +187,30 @@ private static List<Block> BuildBlocks(ArraySegment<byte> bytes, bool useKanji)
167187 continue ;
168188 }
169189
170- blockList . Add ( new Block { Mode = previousMode , Length = i - blockStartIndex } ) ;
190+ blocks [ blockCount ] = new Block { Mode = ( DataSegmentMode ) previousMode , Length = i - blockStartIndex } ;
191+ blockCount += 1 ;
171192 previousMode = currentMode ;
172193 blockStartIndex = i ;
173194 }
174195
175- blockList . Add ( new Block { Mode = previousMode , Length = modes . Length - blockStartIndex } ) ;
196+ blocks [ blockCount ] = new Block { Mode = ( DataSegmentMode ) previousMode , Length = modes . Length - blockStartIndex } ;
197+ return blocks ;
198+ }
176199
177- return blockList ;
200+ private static int CountModeChanges ( byte [ ] modes )
201+ {
202+ var count = 1 ;
203+ var previousMode = modes [ 0 ] ;
204+ for ( var i = 0 ; i < modes . Length ; i += 1 )
205+ {
206+ var currentMode = modes [ i ] ;
207+ if ( currentMode != previousMode )
208+ {
209+ count += 1 ;
210+ previousMode = currentMode ;
211+ }
212+ }
213+ return count ;
178214 }
179215
180216 /// <summary>
@@ -190,31 +226,31 @@ private static List<Block> BuildBlocks(ArraySegment<byte> bytes, bool useKanji)
190226 /// <param name="bytes">The bytes to encode.</param>
191227 /// <param name="useKanji">If <c>true</c>, Kanji encoding is considered; if <c>false</c>, Kanji encoding is not used.</param>
192228 /// <returns>An array of the best encoding mode for each byte.</returns>
193- private static DataSegmentMode [ ] CalcCompactionMode ( ArraySegment < byte > bytes , bool useKanji )
229+ private static byte [ ] CalcCompactionMode ( ArraySegment < byte > bytes , bool useKanji )
194230 {
195231 var len = bytes . Count ;
196- var modes = new DataSegmentMode [ len ] ;
232+ var modes = new byte [ len ] ;
197233 var index = 0 ;
198234 while ( index < len )
199235 {
200236 var b1 = bytes . At ( index ) ;
201237 if ( DataSegmentNumeric . IsNumeric ( b1 ) )
202238 {
203- modes [ index ] = DataSegmentMode . Numeric ;
239+ modes [ index ] = ( byte ) DataSegmentMode . Numeric ;
204240 }
205241 else if ( DataSegmentAlphanumeric . IsAlphanumeric ( b1 ) )
206242 {
207- modes [ index ] = DataSegmentMode . Alphanumeric ;
243+ modes [ index ] = ( byte ) DataSegmentMode . Alphanumeric ;
208244 }
209245 else if ( useKanji && index < len - 1 && DataSegmentKanji . IsShiftJisDoubleByte ( b1 , bytes . At ( index + 1 ) ) )
210246 {
211- modes [ index ] = DataSegmentMode . Kanji ;
247+ modes [ index ] = ( byte ) DataSegmentMode . Kanji ;
212248 index += 1 ;
213- modes [ index ] = DataSegmentMode . Kanji ;
249+ modes [ index ] = ( byte ) DataSegmentMode . Kanji ;
214250 }
215251 else
216252 {
217- modes [ index ] = DataSegmentMode . Binary ;
253+ modes [ index ] = ( byte ) DataSegmentMode . Binary ;
218254 }
219255
220256 index += 1 ;
@@ -230,7 +266,7 @@ private static DataSegmentMode[] CalcCompactionMode(ArraySegment<byte> bytes, bo
230266 /// <summary>
231267 /// Block of bytes with an associated encoding mode.
232268 /// </summary>
233- private struct Block
269+ internal struct Block
234270 {
235271 /// <summary>
236272 /// Encoding mode.
@@ -249,7 +285,21 @@ private struct Block
249285 /// <returns>Resulting segment length, in bits.</returns>
250286 internal int GetSegmentLength ( int version )
251287 {
252- return DataSegment . GetBitLength ( Mode , Length , version ) ;
288+ // Duplicated code for performance
289+ switch ( Mode )
290+ {
291+ case DataSegmentMode . Binary :
292+ return 12 + ( version <= 9 ? 0 : 8 ) + Length * 8 ;
293+ case DataSegmentMode . Numeric :
294+ return 14 + ( version + 7 ) / 17 * 2 + ( Length * 10 + 2 ) / 3 ;
295+ case DataSegmentMode . Alphanumeric :
296+ return 13 + ( version + 7 ) / 17 * 2 + ( Length * 11 + 1 ) / 2 ;
297+ case DataSegmentMode . Kanji :
298+ return 12 + ( version + 7 ) / 17 * 2 + Length * 13 / 2 ;
299+ }
300+
301+ Debug . Assert ( false ) ;
302+ return 0 ;
253303 }
254304
255305 public override string ToString ( )
0 commit comments