Skip to content

Commit ee9ea96

Browse files
committed
Encoding information
1 parent d8de1be commit ee9ea96

10 files changed

Lines changed: 134 additions & 82 deletions

File tree

QrCodeAnalyzer/MainWindow.xaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,24 @@
5555
<Border Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Gray" BorderThickness="0,0,0,1" VerticalAlignment="Bottom"/>
5656
<Label Content="Horz. Streaks:" Grid.Row="7" Grid.Column="0" Background="Transparent" Cursor="Hand" Tag="HorizontalStreaks"
5757
PreviewMouseLeftButtonDown="HighlightLabel_MouseDown" PreviewMouseLeftButtonUp="HighlightLabel_MouseUp"/>
58-
<Label Content="{Binding Path=PenaltyDetails.HorizontalStreaks}" Grid.Row="7" Grid.Column="1" Background="Transparent" Cursor="Hand" Tag="HorizontalStreaks"
58+
<Label Content="{Binding Path=PenaltyInfo.HorizontalStreaks}" Grid.Row="7" Grid.Column="1" Background="Transparent" Cursor="Hand" Tag="HorizontalStreaks"
5959
PreviewMouseLeftButtonDown="HighlightLabel_MouseDown" PreviewMouseLeftButtonUp="HighlightLabel_MouseUp"/>
6060
<Label Content="Vert. Streaks:" Grid.Row="8" Grid.Column="0" Background="Transparent" Cursor="Hand" Tag="VerticalStreaks"
6161
PreviewMouseLeftButtonDown="HighlightLabel_MouseDown" PreviewMouseLeftButtonUp="HighlightLabel_MouseUp"/>
62-
<Label Content="{Binding Path=PenaltyDetails.VerticalStreaks}" Grid.Row="8" Grid.Column="1" Background="Transparent" Cursor="Hand" Tag="VerticalStreaks"
62+
<Label Content="{Binding Path=PenaltyInfo.VerticalStreaks}" Grid.Row="8" Grid.Column="1" Background="Transparent" Cursor="Hand" Tag="VerticalStreaks"
6363
PreviewMouseLeftButtonDown="HighlightLabel_MouseDown" PreviewMouseLeftButtonUp="HighlightLabel_MouseUp"/>
6464
<Label Content="Boxes:" Grid.Row="9" Grid.Column="0" Background="Transparent" Cursor="Hand" Tag="Blocks"
6565
PreviewMouseLeftButtonDown="HighlightLabel_MouseDown" PreviewMouseLeftButtonUp="HighlightLabel_MouseUp"/>
66-
<Label Content="{Binding Path=PenaltyDetails.Blocks}" Grid.Row="9" Grid.Column="1" Background="Transparent" Cursor="Hand" Tag="Blocks"
66+
<Label Content="{Binding Path=PenaltyInfo.Blocks}" Grid.Row="9" Grid.Column="1" Background="Transparent" Cursor="Hand" Tag="Blocks"
6767
PreviewMouseLeftButtonDown="HighlightLabel_MouseDown" PreviewMouseLeftButtonUp="HighlightLabel_MouseUp"/>
6868
<Label Content="Horz. Finder:" Grid.Row="10" Grid.Column="0"/>
69-
<Label Content="{Binding Path=PenaltyDetails.HorizontalFinderPatterns}" Grid.Row="10" Grid.Column="1"/>
69+
<Label Content="{Binding Path=PenaltyInfo.HorizontalFinderPatterns}" Grid.Row="10" Grid.Column="1"/>
7070
<Label Content="Vert. Finder:" Grid.Row="11" Grid.Column="0"/>
71-
<Label Content="{Binding Path=PenaltyDetails.VerticalFinderPatterns}" Grid.Row="11" Grid.Column="1"/>
71+
<Label Content="{Binding Path=PenaltyInfo.VerticalFinderPatterns}" Grid.Row="11" Grid.Column="1"/>
7272
<Label Content="Balance:" Grid.Row="12" Grid.Column="0"/>
73-
<Label Content="{Binding Path=PenaltyDetails.ColorBalance}" Grid.Row="12" Grid.Column="1"/>
73+
<Label Content="{Binding Path=PenaltyInfo.ColorBalance}" Grid.Row="12" Grid.Column="1"/>
7474
<Label Content="Total:" Grid.Row="13" Grid.Column="0"/>
75-
<Label Content="{Binding Path=PenaltyDetails.Total}" Grid.Row="13" Grid.Column="1"/>
75+
<Label Content="{Binding Path=PenaltyInfo.Total}" Grid.Row="13" Grid.Column="1"/>
7676
</Grid>
7777
<TextBox x:Name="QrCodeTextBox" Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}"
7878
Grid.Row="1" Grid.ColumnSpan="2"

QrCodeAnalyzer/MainWindowViewModel.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,23 @@ public sealed record SegmentRow(string Mode, string Content);
2424

2525
public sealed class MainWindowViewModel : INotifyPropertyChanged
2626
{
27-
private readonly QrCodeBuilder.DebugInfo _debugInfo = new QrCodeBuilder.DebugInfo();
28-
2927
private string _text = "QR code text";
3028
private int _borderWidth = 4;
3129
private QrCode.Ecc _errorCorrection = QrCode.Ecc.Medium;
3230
private bool fixedEcc = false;
3331
private int _dataMaskPattern = -1;
34-
private QrCodeBuilder.PenaltyInfo _penaltyDetails;
32+
private PenaltyScore _penaltyScore;
3533
private ImageSource? _qrCodeImage;
3634
private int _selectedDataMaskPattern = -1;
3735
private QrCode? _currentQrCode;
3836
private HighlightKind _highlight = HighlightKind.None;
39-
private IReadOnlyList<SegmentRow> _dataSegments = Array.Empty<SegmentRow>();
37+
private IReadOnlyList<SegmentRow> _dataSegments = [];
4038
private int _qrCodeVersion;
4139
private string _qrCodeSize = string.Empty;
4240
private QrCode.Ecc _selectedEcc;
4341

4442
public MainWindowViewModel()
4543
{
46-
QrCodeBuilder.DebugAccess = _debugInfo;
4744
ErrorCorrectionLevels =
4845
[
4946
new Tuple<string, QrCode.Ecc>("Low", QrCode.Ecc.Low),
@@ -143,12 +140,12 @@ private set
143140
}
144141
}
145142

146-
public QrCodeBuilder.PenaltyInfo PenaltyDetails
143+
public PenaltyScore PenaltyInfo
147144
{
148-
get => _penaltyDetails;
145+
get => _penaltyScore;
149146
private set
150147
{
151-
_penaltyDetails = value;
148+
_penaltyScore = value;
152149
OnPropertyChanged();
153150
}
154151
}
@@ -226,16 +223,19 @@ public BitmapSource CreateClipboardBitmap()
226223

227224
private void UpdateQrCode()
228225
{
229-
_debugInfo.ForcedDataMask = _dataMaskPattern;
230-
var qrCode = QrCode.EncodeTextAdvanced(_text, _errorCorrection, boostEcl: !fixedEcc);
226+
EncodingInfo encodingInfo = new()
227+
{
228+
ForcedDataMask = _dataMaskPattern
229+
};
230+
var qrCode = QrCode.EncodeTextAdvanced(_text, _errorCorrection, boostEcl: !fixedEcc, encodingInfo: encodingInfo);
231231
_currentQrCode = qrCode;
232232
SelectedDataMaskPattern = qrCode.Mask;
233-
PenaltyDetails = _debugInfo.Penalties[qrCode.Mask];
233+
PenaltyInfo = encodingInfo.Penalties[qrCode.Mask];
234234
QrCodeVersion = qrCode.Version;
235235
QrCodeSize = $"{qrCode.Size}×{qrCode.Size}";
236236
SelectedEcc = qrCode.ErrorCorrectionLevel;
237-
DataSegments = _debugInfo.DataSegments?.Select(BuildSegmentRow).ToArray()
238-
?? (IReadOnlyList<SegmentRow>)Array.Empty<SegmentRow>();
237+
DataSegments = encodingInfo.DataSegments?.Select(BuildSegmentRow).ToArray()
238+
?? (IReadOnlyList<SegmentRow>)[];
239239
RenderQrCodeImage();
240240
}
241241

@@ -258,7 +258,7 @@ private static string DecodeAscii(ArraySegment<byte> data)
258258
return data.Array == null ? string.Empty : Encoding.ASCII.GetString(data.Array, data.Offset, data.Count);
259259
}
260260

261-
private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(false, throwOnInvalidBytes: true);
261+
private static readonly UTF8Encoding StrictUtf8 = new(false, throwOnInvalidBytes: true);
262262

263263
private static string FormatByteContent(ArraySegment<byte> data)
264264
{

QrCodeAnalyzer/QrCodeAnalyzer.csproj

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<Reference Include="QrCodeGenerator">
15-
<HintPath>..\QrCodeGenerator\bin\Debug\netstandard2.0\QrCodeGenerator.dll</HintPath>
16-
</Reference>
14+
<PackageReference Include="Net.Codecrete.QrCodeGenerator" Version="3.0.0" />
1715
</ItemGroup>
1816

1917
</Project>

QrCodeGenerator/DataSegmentStructuredAppend.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Net.Codecrete.QrCodeGenerator
55
/// <summary>
66
/// Data segment for the structured append header.
77
/// </summary>
8-
public class DataSegmentStructuredAppend : DataSegment
8+
internal class DataSegmentStructuredAppend : DataSegment
99
{
1010
private const int StructuredAppendBitLength = 16;
1111

QrCodeGenerator/EncodingInfo.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Collections.Generic;
2+
3+
namespace Net.Codecrete.QrCodeGenerator
4+
{
5+
/// <summary>
6+
/// Details about the QR code encoding.
7+
/// <para>
8+
/// The details can be collected during QR code generation for analysis purposes,
9+
/// but they are not used by the library itself. Collecting the information will make
10+
/// the QR code generation slower as the library will fully calcuate the penalty score for
11+
/// all data mask patterns, even if it is already clear that the pattern is not the best one.
12+
/// </para>
13+
/// </summary>
14+
public class EncodingInfo
15+
{
16+
/// <summary>
17+
/// The penalty score for all eight possible data mask patterns.
18+
/// </summary>
19+
public PenaltyScore[] Penalties { get; } = new PenaltyScore[8];
20+
/// <summary>
21+
/// The data segments used to represent the payload data.
22+
/// </summary>
23+
public List<DataSegment> DataSegments { get; set; }
24+
/// <summary>
25+
/// The data mask to be forcibly applied to the data source, overriding the result of the automatic mask selection.
26+
/// <para>
27+
/// Set this property to a value between 0 and 7 to specify a particular data mask.
28+
/// A value of -1 indicates that the best data mask will be automatically selected.
29+
/// </para>
30+
/// </summary>
31+
public int ForcedDataMask { get; set; } = -1;
32+
}
33+
}

QrCodeGenerator/Penalty.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ internal static int CalculatePenalty(BitMatrix modules, BitMatrix transposed, in
6161
return sum + CalcColorBalance(modules);
6262
}
6363

64-
#if DEBUG
65-
internal static int CalculatePenaltyDebug(BitMatrix modules, BitMatrix transposed, ref QrCodeBuilder.PenaltyInfo penaltyInfo)
64+
// Calculates the total penalty score, fully evaluating all contributions and collecting the details in penaltyInfo.
65+
internal static int CalculatePenaltyFully(BitMatrix modules, BitMatrix transposed, ref PenaltyScore penaltyInfo)
6666
{
6767
penaltyInfo.Blocks = Calc2By2Blocks(modules);
6868
penaltyInfo.VerticalStreaks = CalcSameColor(transposed);
@@ -77,7 +77,6 @@ internal static int CalculatePenaltyDebug(BitMatrix modules, BitMatrix transpose
7777
+ penaltyInfo.ColorBalance;
7878
return penaltyInfo.Total;
7979
}
80-
#endif
8180

8281
internal static int CalcSameColor(BitMatrix modules)
8382
{

QrCodeGenerator/PenaltyScore.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* QR code generator library (.NET)
3+
*
4+
* Copyright (c) Manuel Bleichenbacher (MIT License)
5+
* https://github.com/manuelbl/QrCodeGenerator
6+
*/
7+
8+
namespace Net.Codecrete.QrCodeGenerator
9+
{
10+
/// <summary>
11+
/// Information about the penalty score of a data mask pattern.
12+
/// <para>
13+
/// QR code use one out of eight patterns to improve the readability of the code.
14+
/// The penalty score measures how difficult it is to read a QR code with a given pattern.
15+
/// </para>
16+
/// <para>
17+
/// The penaly score information can be collected for analysis purposes, but it is not used by the library itself.
18+
/// If the information is collected, the QR code generation will be slower as the library needs to
19+
/// fully calculate the penalty score even if it is already clear that the pattern is not the best one.
20+
/// </para>
21+
/// <para>
22+
/// Note that in deviation from the QR code specification, the penalty score does not include the score
23+
/// contributed by the finder patterns.
24+
/// </para>
25+
/// </summary>
26+
public struct PenaltyScore
27+
{
28+
/// <summary>
29+
/// Penalty score for long horizontal runs of the same color.
30+
/// </summary>
31+
public int HorizontalStreaks { get; set; }
32+
/// <summary>
33+
/// Penalty score for long vertical runs of the same color.
34+
/// </summary>
35+
public int VerticalStreaks { get; set; }
36+
/// <summary>
37+
/// Penalty score for blocks of 2x2 modules of the same color.
38+
/// </summary>
39+
public int Blocks { get; set; }
40+
/// <summary>
41+
/// Penalty score for patterns that look like the finder patterns (1:1:3:1:1 ratio) in the horizontal direction.
42+
/// </summary>
43+
public int HorizontalFinderPatterns { get; set; }
44+
/// <summary>
45+
/// Penalty score for patterns that look like the finder patterns (1:1:3:1:1 ratio) in the vertical direction.
46+
/// </summary>
47+
public int VerticalFinderPatterns { get; set; }
48+
/// <summary>
49+
/// Penalty score for an inbalance of the number of dark and light modules.
50+
/// </summary>
51+
public int ColorBalance { get; set; }
52+
/// <summary>
53+
/// The total penalty score for the data mask pattern.
54+
/// </summary>
55+
public int Total { get; set; }
56+
}
57+
}

QrCodeGenerator/QrCode.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public static QrCode EncodeText(string text, Ecc ecl)
126126
/// <param name="encoding">The character set encoding to use,
127127
/// or <c>null</c> if it should be derived from the ECI.</param>
128128
/// <param name="kanjiStrategy">Controls if Kanji mode is used for data segments.</param>
129+
/// <param name="encodingInfo">If an instance is provided, it will be filled with information about the QR code encoding.
130+
/// Collecting the encoding information will slow down the QR code generation.</param>
129131
/// <returns>A QR code instance representing the specified text.</returns>
130132
/// <exception cref="ArgumentNullException">Thrown if <paramref name="text"/> is <c>null</c>.</exception>
131133
/// <exception cref="ArgumentOutOfRangeException">1 &#x2264; minVersion &#x2264; maxVersion &#x2264; 40 is violated.</exception>
@@ -145,15 +147,16 @@ public static QrCode EncodeTextAdvanced(
145147
int maxVersion = MaxVersion,
146148
ECI eci = null,
147149
Encoding encoding = null,
148-
KanjiStrategy kanjiStrategy = KanjiStrategy.Automatic
150+
KanjiStrategy kanjiStrategy = KanjiStrategy.Automatic,
151+
EncodingInfo encodingInfo = null
149152
)
150153
{
151154
Objects.RequireNonNull(text, nameof(text));
152155
ValidateVersion(minVersion, maxVersion);
153156

154157
var segments = DataSegment.FromText(text, eci: eci, encoding: encoding,
155158
version: maxVersion, kanjiStrategy: kanjiStrategy);
156-
return QrCodeBuilder.Build(segments, (int) minimumEcl, minVersion, maxVersion, boostEcl);
159+
return QrCodeBuilder.Build(segments, (int) minimumEcl, minVersion, maxVersion, boostEcl, encodingInfo);
157160
}
158161

159162
/// <summary>

QrCodeGenerator/QrCodeBuilder.cs

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@
1313

1414
namespace Net.Codecrete.QrCodeGenerator
1515
{
16-
#if DEBUG
17-
public
18-
#else
19-
internal
20-
#endif
21-
static class QrCodeBuilder
16+
internal static class QrCodeBuilder
2217
{
2318
#region Caches
2419

@@ -31,28 +26,27 @@ static class QrCodeBuilder
3126

3227
#region Build
3328

34-
internal static QrCode Build(List<DataSegment> dataSegments, int ecc, int minVersion = 1, int maxVersion = 40, bool boostEcc = true)
29+
internal static QrCode Build(List<DataSegment> dataSegments, int ecc, int minVersion = 1, int maxVersion = 40, bool boostEcc = true, EncodingInfo encodingInfo = null)
3530
{
36-
#if DEBUG
37-
if (DebugAccess != null)
31+
if (encodingInfo != null)
3832
{
39-
DebugAccess.DataSegments = dataSegments;
33+
encodingInfo.DataSegments = dataSegments;
4034
}
41-
#endif
35+
4236
var result = FindVersionAndEcc(dataSegments, ecc, minVersion, maxVersion, boostEcc);
4337
var version = result.Item1;
4438
ecc = result.Item2;
4539

4640
var codewords = BuildCodewords(dataSegments, version, ecc);
4741
codewords = AddErrorCorrection(codewords, version, ecc);
48-
return Build(codewords, ecc, version);
42+
return Build(codewords, ecc, version, encodingInfo);
4943
}
5044

51-
private static QrCode Build(byte[] codewords, int ecc, int version)
45+
private static QrCode Build(byte[] codewords, int ecc, int version, EncodingInfo encodingInfo)
5246
{
5347
var modules = CreateWithFixedPatterns(version);
5448
FillPayload(modules, codewords, version);
55-
var pattern = ApplyBestPattern(modules, version, ecc);
49+
var pattern = ApplyBestPattern(modules, version, ecc, encodingInfo);
5650
return new QrCode(modules, (QrCode.Ecc)ecc, pattern);
5751
}
5852

@@ -697,7 +691,7 @@ private static BitMatrix CreatePattern(int patternIndex, int version)
697691
// "Mask Pattern Selection".
698692
private static readonly int[] PatternEvaluationOrder = { 2, 3, 7, 4, 6, 5, 0, 1 };
699693

700-
private static int ApplyBestPattern(BitMatrix modules, int version, int ecc)
694+
private static int ApplyBestPattern(BitMatrix modules, int version, int ecc, EncodingInfo encodingInfo = null)
701695
{
702696
var transposed = modules.Copy();
703697
transposed.Transpose();
@@ -714,18 +708,14 @@ private static int ApplyBestPattern(BitMatrix modules, int version, int ecc)
714708
modules.Xor(mask);
715709
transposed.Xor(maskT);
716710

717-
#if DEBUG
718711
int penalty;
719-
if (DebugAccess != null) {
720-
penalty = Penalty.CalculatePenaltyDebug(modules, transposed, ref DebugAccess.Penalties[pattern]);
712+
if (encodingInfo != null) {
713+
penalty = Penalty.CalculatePenaltyFully(modules, transposed, ref encodingInfo.Penalties[pattern]);
721714
}
722715
else
723716
{
724717
penalty = Penalty.CalculatePenalty(modules, transposed, lowestPenalty);
725718
}
726-
#else
727-
var penalty = Penalty.CalculatePenalty(modules, transposed, lowestPenalty);
728-
#endif
729719

730720
// undo pattern
731721
modules.Xor(mask);
@@ -737,12 +727,10 @@ private static int ApplyBestPattern(BitMatrix modules, int version, int ecc)
737727
}
738728
}
739729

740-
#if DEBUG
741-
if (DebugAccess != null && DebugAccess.ForcedDataMask >= 0)
730+
if (encodingInfo != null && encodingInfo.ForcedDataMask >= 0)
742731
{
743-
bestPattern = DebugAccess.ForcedDataMask;
732+
bestPattern = encodingInfo.ForcedDataMask;
744733
}
745-
#endif
746734

747735
DrawFormatInformation(modules, ecc, bestPattern);
748736
var bestMask = GetDataMaskPattern(bestPattern, version);
@@ -993,31 +981,5 @@ internal static int GetVersionInformationBits(int version)
993981
};
994982

995983
#endregion
996-
997-
#if DEBUG
998-
#region Debugging
999-
1000-
public struct PenaltyInfo
1001-
{
1002-
public int HorizontalStreaks { get; set; }
1003-
public int VerticalStreaks { get; set; }
1004-
public int Blocks { get; set; }
1005-
public int HorizontalFinderPatterns { get; set; }
1006-
public int VerticalFinderPatterns { get; set; }
1007-
public int ColorBalance { get; set; }
1008-
public int Total { get; set; }
1009-
}
1010-
1011-
public class DebugInfo
1012-
{
1013-
public PenaltyInfo[] Penalties { get; } = new PenaltyInfo[8];
1014-
public int ForcedDataMask { get; set; } = -1;
1015-
public List<DataSegment> DataSegments { get; set; }
1016-
}
1017-
1018-
public static DebugInfo DebugAccess { get; set; }
1019-
1020-
#endregion
1021-
#endif
1022984
}
1023985
}

0 commit comments

Comments
 (0)