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 }