1 module ithox.qrcode.encoder.maskutil;
2 
3 import ithox.qrcode.encoder.bytematrix;
4 import ithox.qrcode.common.bitutils;
5 
6 import std.conv;
7 
8 /**
9 * Mask utility.
10 */
11 class MaskUtil
12 {
13     /**#@+
14 	* Penalty weights from section 6.8.2.1
15 	*/
16     enum N1 = 3;
17     enum N2 = 3;
18     enum N3 = 40;
19     enum N4 = 10;
20     /**#@-*/
21 
22     /**
23 	* Applies mask penalty rule 1 and returns the penalty.
24 	*
25 	* Finds repetitive cells with the same color and gives penalty to them.
26 	* Example: 00000 or 11111.
27 	*
28 	* @param  ByteMatrix $matrix
29 	* @return integer
30 	*/
31     public static int applyMaskPenaltyRule1(ByteMatrix matrix)
32     {
33         return (applyMaskPenaltyRule1Internal(matrix,
34                 true) + applyMaskPenaltyRule1Internal(matrix, false));
35     }
36 
37     /**
38 	* Applies mask penalty rule 2 and returns the penalty.
39 	*
40 	* Finds 2x2 blocks with the same color and gives penalty to them. This is
41 	* actually equivalent to the spec's rule, which is to find MxN blocks and
42 	* give a penalty proportional to (M-1)x(N-1), because this is the number of
43 	* 2x2 blocks inside such a block.
44 	*
45 	* @param  ByteMatrix matrix
46 	* @return integer
47 	*/
48     public static int applyMaskPenaltyRule2(ByteMatrix matrix)
49     {
50         auto penalty = 0;
51         auto array = matrix.getArray();
52         auto width = matrix.width();
53         auto height = matrix.height();
54         for (auto y = 0; y < height - 1; y++)
55         {
56             for (auto x = 0; x < width - 1; x++)
57             {
58                 auto value = array[y][x];
59                 if (value == array[y][x + 1] && value == array[y + 1][x]
60                         && value == array[y + 1][x + 1])
61                 {
62                     penalty++;
63                 }
64             }
65         }
66         return N2 * penalty;
67     }
68 
69     /**
70 	* Helper function for applyMaskPenaltyRule1.
71 	*
72 	* We need this for doing this calculation in both vertical and horizontal
73 	* orders respectively.
74 	*
75 	* @param  ByteMatrix matrix
76 	* @param  boolean    isHorizontal
77 	* @return integer
78 	*/
79     protected static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, bool isHorizontal)
80     {
81         auto penalty = 0;
82         auto iLimit = isHorizontal ? matrix.height() : matrix.width();
83         auto jLimit = isHorizontal ? matrix.width() : matrix.height();
84         auto array = matrix.getArray();
85         for (auto i = 0; i < iLimit; i++)
86         {
87             auto numSameBitCells = 0;
88             auto prevBit = -1;
89             for (auto j = 0; j < jLimit; j++)
90             {
91                 auto bit = isHorizontal ? array[i][j] : array[j][i];
92                 if (bit == prevBit)
93                 {
94                     numSameBitCells++;
95                 }
96                 else
97                 {
98                     if (numSameBitCells >= 5)
99                     {
100                         penalty += N1 + (numSameBitCells - 5);
101                     }
102                     numSameBitCells = 1;
103                     prevBit = bit;
104                 }
105             }
106             if (numSameBitCells >= 5)
107             {
108                 penalty += N1 + (numSameBitCells - 5);
109             }
110         }
111         return penalty;
112     }
113 
114     /**
115 	* Applies mask penalty rule 3 and returns the penalty.
116 	*
117 	* Finds consecutive cells of 00001011101 or 10111010000, and gives penalty
118 	* to them. If we find patterns like 000010111010000, we give penalties
119 	* twice (i.e. 40 * 2).
120 	*
121 	* @param  ByteMatrix matrix
122 	* @return integer
123 	*/
124     public static int applyMaskPenaltyRule3(ByteMatrix matrix)
125     {
126         auto penalty = 0;
127         auto array = matrix.getArray();
128         auto width = matrix.width();
129         auto height = matrix.height();
130         for (auto y = 0; y < height; y++)
131         {
132             for (auto x = 0; x < width; x++)
133             {
134                 if (x + 6 < width && array[y][x] == 1 && array[y][x + 1] == 0
135                         && array[y][x + 2] == 1 && array[y][x + 3] == 1
136                         && array[y][x + 4] == 1 && array[y][x + 5] == 0
137                         && array[y][x + 6] == 1 && ((x + 10 < width
138                         && array[y][x + 7] == 0 && array[y][x + 8] == 0
139                         && array[y][x + 9] == 0 && array[y][x + 10] == 0)
140                         || (x - 4 >= 0 && array[y][x - 1] == 0
141                         && array[y][x - 2] == 0 && array[y][x - 3] == 0 && array[y][x - 4] == 0)))
142                 {
143                     penalty += N3;
144                 }
145                 if (y + 6 < height && array[y][x] == 1 && array[y + 1][x] == 0
146                         && array[y + 2][x] == 1 && array[y + 3][x] == 1
147                         && array[y + 4][x] == 1 && array[y + 5][x] == 0
148                         && array[y + 6][x] == 1 && ((y + 10 < height
149                         && array[y + 7][x] == 0 && array[y + 8][x] == 0
150                         && array[y + 9][x] == 0 && array[y + 10][x] == 0)
151                         || (y - 4 >= 0 && array[y - 1][x] == 0
152                         && array[y - 2][x] == 0 && array[y - 3][x] == 0 && array[y - 4][x] == 0)))
153                 {
154                     penalty += N3;
155                 }
156             }
157         }
158         return penalty;
159     }
160 
161     /**
162 	* Applies mask penalty rule 4 and returns the penalty.
163 	*
164 	* Calculates the ratio of dark cells and gives penalty if the ratio is far
165 	* from 50%. It gives 10 penalty for 5% distance.
166 	*
167 	* @param  ByteMatrix matrix
168 	* @return integer
169 	*/
170     public static int applyMaskPenaltyRule4(ByteMatrix matrix)
171     {
172         auto numDarkCells = 0;
173         auto array = matrix.getArray();
174         auto width = matrix.width();
175         auto height = matrix.height();
176         for (auto y = 0; y < height; y++)
177         {
178             auto arrayY = array[y];
179             for (auto x = 0; x < width; x++)
180             {
181                 if (arrayY[x] == 1)
182                 {
183                     numDarkCells++;
184                 }
185             }
186         }
187         auto numTotalCells = height * width;
188         auto darkRatio = numDarkCells / numTotalCells;
189         import std.math;
190 
191         auto fixedPercentVariances = to!(int)(abs(darkRatio - 0.5) * 20);
192         return fixedPercentVariances * N4;
193     }
194 
195     /**
196 	* Returns the mask bit for "getMaskPattern" at "x" and "y".
197 	*
198 	* See 8.8 of JISX0510:2004 for mask pattern conditions.
199 	*
200 	* @param  integer maskPattern
201 	* @param  integer x
202 	* @param  integer y
203 	* @return integer
204 	* @throws Exception\InvalidArgumentException
205 	*/
206     public static int getDataMaskBit(int maskPattern, int x, int y)
207     {
208         auto intermediate = 0;
209         switch (maskPattern)
210         {
211         case 0:
212             intermediate = (y + x) & 0x1;
213             break;
214         case 1:
215             intermediate = y & 0x1;
216             break;
217         case 2:
218             intermediate = x % 3;
219             break;
220         case 3:
221             intermediate = (y + x) % 3;
222             break;
223         case 4:
224             intermediate = (BitUtils.unsignedRightShift(y, 1) + (x / 3)) & 0x1;
225             break;
226         case 5:
227             auto temp = y * x;
228             intermediate = (temp & 0x1) + (temp % 3);
229             break;
230         case 6:
231             auto temp = y * x;
232             intermediate = ((temp & 0x1) + (temp % 3)) & 0x1;
233             break;
234         case 7:
235             auto temp = y * x;
236             intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1;
237             break;
238         default:
239             throw new Exception("Invalid mask pattern: " ~ to!string(maskPattern));
240         }
241         return intermediate == 0;
242     }
243 }