Skip to content

Commit 9a9d013

Browse files
committed
Separate class for fixed patterns
1 parent d8b106b commit 9a9d013

4 files changed

Lines changed: 395 additions & 278 deletions

File tree

CONTEXT.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# QrCodeGenerator Context
2+
3+
Domain language for the QR code generation library. These are the terms specific
4+
to QR code structure and this library's layout pipeline — use them consistently in
5+
code, comments, and discussion.
6+
7+
## Language
8+
9+
**Module**:
10+
A single pixel of a QR code (dark or light); the unit the standard counts by.
11+
_Avoid_: pixel (reserve "pixel" for rendered output), cell.
12+
13+
**Fixed patterns**:
14+
The modules placed identically for a given version regardless of payload — finder
15+
patterns, separators, timing patterns, alignment patterns, and version information —
16+
together with the reserved area for format information.
17+
_Avoid_: function patterns (the ISO/IEC 18004 term; we say "fixed patterns").
18+
19+
**Footprint**:
20+
The area a fixed pattern occupies, independent of which modules within it are dark.
21+
A footprint is reserved even where its modules are light (e.g. separators, the light
22+
rings of a finder, format/version `0` bits).
23+
24+
**Reserved modules**:
25+
The union of all fixed-pattern footprints — every module the payload must not use.
26+
27+
**Payload-area map**:
28+
The complement of the reserved modules: the modules the payload zig-zag is allowed
29+
to fill. This is what `GetDataMask` returns.
30+
_Avoid_: "data mask" — see Flagged ambiguities.
31+
32+
**Mask pattern**:
33+
One of the 8 XOR patterns (index 0–7) applied to the payload area and chosen by
34+
lowest penalty score. Exposed as `QrCode.Mask`.
35+
36+
## Relationships
37+
38+
- A **version** (1–40) fully determines the **fixed patterns** and therefore the
39+
**reserved modules** and the **payload-area map**.
40+
- The **reserved modules** are the union of every fixed-pattern **footprint**;
41+
the **payload-area map** is their complement.
42+
- Every dark **module** drawn by a fixed pattern lies within the **reserved modules**
43+
(the load-bearing invariant: drawn ⊆ reserved).
44+
- A **mask pattern** is XORed only over the **payload-area map**, never over
45+
**reserved modules**.
46+
47+
## Example dialogue
48+
49+
> **Dev:** "If I move an alignment pattern, do I update the drawn matrix and the
50+
> reserved modules separately?"
51+
> **Domain expert:** "No — they're two views of the same **fixed patterns**. One walk
52+
> emits both: it stamps the dark **modules** and reserves the **footprint** in the same
53+
> place. You can't infer reserved from drawn, because a footprint reserves light modules
54+
> too."
55+
56+
## Flagged ambiguities
57+
58+
- **"data mask"** was used for two distinct things: (1) the **payload-area map**
59+
returned by `GetDataMask` / `DataMaskCache`, and (2) a **mask pattern** (the 8 XOR
60+
patterns), as in `GetDataMaskPattern`. Resolved: prefer **payload-area map** for (1)
61+
and **mask pattern** for (2). The existing `GetDataMask` method name is retained for
62+
compatibility but denotes the payload-area map.

QrCodeGenerator/FixedPatterns.cs

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
/*
2+
* QR code generator library (.NET)
3+
*
4+
* Copyright (c) Manuel Bleichenbacher (MIT License)
5+
* https://github.com/manuelbl/QrCodeGenerator
6+
*/
7+
8+
using System.Collections.Concurrent;
9+
10+
namespace Net.Codecrete.QrCodeGenerator
11+
{
12+
/// <summary>
13+
/// Single source of truth for the fixed-pattern geometry of a QR code version.
14+
/// <para>
15+
/// The fixed patterns are the modules placed identically for a given version
16+
/// regardless of payload: finder patterns, separators, timing patterns,
17+
/// alignment patterns and version information, plus the reserved area for the
18+
/// format information.
19+
/// </para>
20+
/// <para>
21+
/// <see cref="BuildFixedPatterns"/> produces two views of the same geometry in a
22+
/// single walk:
23+
/// </para>
24+
/// <list type="bullet">
25+
/// <item><c>drawn</c> — the actual dark/light modules of the fixed patterns.</item>
26+
/// <item><c>reserved</c> — the union of all fixed-pattern footprints, i.e. every
27+
/// module the payload must not use. A footprint is reserved even where its modules
28+
/// are light (separators, the light rings of a finder, format/version <c>0</c> bits),
29+
/// so the reserved view cannot be derived from the drawn one.</item>
30+
/// </list>
31+
/// <para>
32+
/// The format information is reserve-only here; its bits depend on the error
33+
/// correction level and the chosen mask pattern and are drawn later by
34+
/// <see cref="QrCodeBuilder.DrawFormatInformation(BitMatrix, int, int)"/>.
35+
/// </para>
36+
/// </summary>
37+
internal static class FixedPatterns
38+
{
39+
#region Caches
40+
41+
// version -> (drawn modules, payload-area map). The payload-area map is the
42+
// inverted reserved mask (the modules the payload zig-zag may fill). Both are
43+
// computed by a single BuildFixedPatterns walk and cached together.
44+
// The cached instances are shared and must not be mutated (CreateWithFixedPatterns
45+
// hands out a Copy; GetDataMask's result is treated as read-only by callers).
46+
private static readonly ConcurrentDictionary<int, (BitMatrix Drawn, BitMatrix DataMask)> Cache
47+
= new ConcurrentDictionary<int, (BitMatrix, BitMatrix)>();
48+
49+
private static (BitMatrix Drawn, BitMatrix DataMask) GetCached(int version)
50+
{
51+
return Cache.GetOrAdd(version, ComputeCached);
52+
}
53+
54+
private static (BitMatrix Drawn, BitMatrix DataMask) ComputeCached(int version)
55+
{
56+
var (drawn, reserved) = BuildFixedPatterns(version);
57+
// Turn the reserved mask into the payload-area map (where data goes).
58+
reserved.Invert();
59+
return (drawn, reserved);
60+
}
61+
62+
#endregion
63+
64+
#region Accessors
65+
66+
/// <summary>
67+
/// Creates a <see cref="BitMatrix"/> for the given version with the fixed
68+
/// patterns (finder, timing, alignment, version) already drawn.
69+
/// </summary>
70+
/// <param name="version">The QR code version.</param>
71+
/// <returns>A new (mutable) <see cref="BitMatrix"/> instance.</returns>
72+
internal static BitMatrix CreateWithFixedPatterns(int version)
73+
{
74+
return GetCached(version).Drawn.Copy();
75+
}
76+
77+
/// <summary>
78+
/// Returns the payload-area map for the given version: a <see cref="BitMatrix"/>
79+
/// with all bits set where payload data goes (the complement of the reserved
80+
/// modules).
81+
/// <para>
82+
/// The returned matrix is shared and cached. Callers must not mutate it.
83+
/// </para>
84+
/// </summary>
85+
/// <param name="version">The QR code version.</param>
86+
/// <returns>A shared <see cref="BitMatrix"/> instance.</returns>
87+
internal static BitMatrix GetDataMask(int version)
88+
{
89+
return GetCached(version).DataMask;
90+
}
91+
92+
#endregion
93+
94+
#region Single walk
95+
96+
/// <summary>
97+
/// Builds the fixed-pattern geometry for the given version in a single walk.
98+
/// <para>
99+
/// Each region stamps its dark modules into <c>drawn</c> and reserves its
100+
/// footprint into <c>reserved</c> in the same place, so the two views cannot
101+
/// drift apart. The drawn invariant <c>drawn AND NOT reserved == 0</c> holds
102+
/// by construction.
103+
/// </para>
104+
/// <para>
105+
/// Draw order is load-bearing: alignment patterns are drawn after the timing
106+
/// patterns so they overwrite the timing line where the two overlap.
107+
/// </para>
108+
/// </summary>
109+
/// <param name="version">The QR code version.</param>
110+
/// <returns>The drawn modules and the reserved-module mask.</returns>
111+
internal static (BitMatrix Drawn, BitMatrix Reserved) BuildFixedPatterns(int version)
112+
{
113+
var size = QrCodeBuilder.GetSize(version);
114+
var drawn = new BitMatrix(size);
115+
var reserved = new BitMatrix(size);
116+
117+
// Single asymmetric dark module
118+
drawn.Set(8, size - 8, true);
119+
reserved.Set(8, size - 8, true);
120+
121+
// Format information (reserve-only; bits drawn later, ecc/mask-dependent)
122+
ReserveFormatInformation(reserved);
123+
124+
// Version information (versions 7 and up)
125+
DrawVersionInformation(drawn, version);
126+
ReserveVersionInformation(reserved, version);
127+
128+
// Finder patterns and adjacent separators (footprint 8x8, pattern 7x7)
129+
DrawFinderPattern(drawn, 0, 0);
130+
DrawFinderPattern(drawn, size - 7, 0);
131+
DrawFinderPattern(drawn, 0, size - 7);
132+
reserved.FillRect(0, 0, 8, 8);
133+
reserved.FillRect(size - 8, 0, 8, 8);
134+
reserved.FillRect(0, size - 8, 8, 8);
135+
136+
// Timing patterns
137+
DrawTimingPattern(drawn);
138+
reserved.FillRect(8, 6, size - 16, 1);
139+
reserved.FillRect(6, 8, 1, size - 16);
140+
141+
// Alignment patterns (drawn after timing so they overwrite it on overlap)
142+
DrawAndReserveAlignmentPatterns(drawn, reserved, version);
143+
144+
return (drawn, reserved);
145+
}
146+
147+
#endregion
148+
149+
#region Finder patterns
150+
151+
private static void DrawFinderPattern(BitMatrix modules, int x, int y)
152+
{
153+
for (var i = 0; i < 7; i += 1)
154+
{
155+
modules.Set(x + i, y, true);
156+
modules.Set(x + i, y + 6, true);
157+
}
158+
159+
for (var i = 1; i < 6; i += 1)
160+
{
161+
modules.Set(x, y + i, true);
162+
modules.Set(x + 1, y + i, false);
163+
modules.Set(x + 5, y + i, false);
164+
modules.Set(x + 6, y + i, true);
165+
}
166+
167+
for (var i = 2; i < 5; i += 1)
168+
{
169+
modules.Set(x + i, y + 1, false);
170+
modules.Set(x + i, y + 5, false);
171+
}
172+
173+
for (var i = 2; i < 5; i += 1)
174+
{
175+
modules.Set(x + i, y + 2, true);
176+
modules.Set(x + i, y + 3, true);
177+
modules.Set(x + i, y + 4, true);
178+
}
179+
}
180+
181+
#endregion
182+
183+
#region Alignment patterns
184+
185+
private static void DrawAndReserveAlignmentPatterns(BitMatrix drawn, BitMatrix reserved, int version)
186+
{
187+
if (version == 1)
188+
{
189+
// no alignment patterns in version 1
190+
return;
191+
}
192+
193+
var positions = QrCodeBuilder.GetAlignmentPatternPosition(version);
194+
var numPositions = positions.Length;
195+
196+
for (var x = 0; x < numPositions; x += 1)
197+
{
198+
for (var y = 0; y < numPositions; y += 1)
199+
{
200+
if ((x == 0 && y == 0) || (x == numPositions - 1 && y == 0) || (x == 0 && y == numPositions - 1))
201+
{
202+
// no alignment patterns near the 3 finder patterns
203+
continue;
204+
}
205+
206+
reserved.FillRect(positions[x] - 2, positions[y] - 2, 5, 5);
207+
DrawAlignmentPattern(drawn, positions[x], positions[y]);
208+
}
209+
}
210+
}
211+
212+
private static void DrawAlignmentPattern(BitMatrix modules, int x, int y)
213+
{
214+
for (var i = -2; i <= 2; i += 1)
215+
{
216+
modules.Set(x + i, y - 2, true);
217+
modules.Set(x + i, y + 2, true);
218+
}
219+
220+
for (var i = -1; i <= 1; i += 1)
221+
{
222+
modules.Set(x - 2, y + i, true);
223+
modules.Set(x + 2, y + i, true);
224+
}
225+
226+
modules.Set(x, y, true);
227+
}
228+
229+
#endregion
230+
231+
#region Timing patterns
232+
233+
private static void DrawTimingPattern(BitMatrix modules)
234+
{
235+
var size = modules.Size;
236+
for (var x = 8; x < size - 8; x += 1)
237+
{
238+
var isDark = ((x + 1) & 1) != 0;
239+
modules.Set(x, 6, isDark);
240+
modules.Set(6, x, isDark);
241+
}
242+
}
243+
244+
#endregion
245+
246+
#region Version information
247+
248+
private static void ReserveVersionInformation(BitMatrix reserved, int version)
249+
{
250+
if (version < 7)
251+
{
252+
// no version information for versions 1-6
253+
return;
254+
}
255+
256+
var size = reserved.Size;
257+
// left bottom corner
258+
reserved.FillRect(0, size - 11, 6, 3);
259+
260+
// top right corner
261+
reserved.FillRect(size - 11, 0, 3, 6);
262+
}
263+
264+
private static void DrawVersionInformation(BitMatrix modules, int version)
265+
{
266+
if (version < 7)
267+
{
268+
// no version information for versions 1-6
269+
return;
270+
}
271+
272+
var size = modules.Size;
273+
var bits = QrCodeBuilder.GetVersionInformationBits(version);
274+
275+
for (var bit = 0; bit < 18; bit += 1)
276+
{
277+
var isDark = (bits & (1 << bit)) != 0;
278+
var x = bit / 3;
279+
var y = bit % 3;
280+
281+
// left bottom corner
282+
modules.Set(x, size - 11 + y, isDark);
283+
284+
// top right corner
285+
modules.Set(size - 11 + y, x, isDark);
286+
}
287+
}
288+
289+
#endregion
290+
291+
#region Format information
292+
293+
private static void ReserveFormatInformation(BitMatrix reserved)
294+
{
295+
reserved.FillRect(8, 0, 1, 9);
296+
reserved.FillRect(0, 8, 8, 1);
297+
reserved.FillRect(reserved.Size - 8, 8, 8, 1);
298+
reserved.FillRect(8, reserved.Size - 7, 1, 7);
299+
}
300+
301+
#endregion
302+
}
303+
}

0 commit comments

Comments
 (0)