1 module ithox.qrcode.common.formatinformation; 2 3 import ithox.qrcode.common.errorcorrectionlevel; 4 import ithox.qrcode.common.bitutils; 5 6 /** 7 * Encapsulates a QR Code's format information, including the data mask used and 8 * error correction level. 9 */ 10 class FormatInformation 11 { 12 /** 13 * Mask for format information. 14 */ 15 enum int FORMAT_INFO_MASK_QR = 0x5412; 16 17 /** 18 * Lookup table for decoding format information. 19 * 20 * See ISO 18004:2006, Annex C, Table C.1 21 * 22 * @var array 23 */ 24 protected static int[][] formatInfoDecodeLookup = [ 25 [0x5412, 0x00 26 ], [0x5125, 0x01], [ 27 0x5e7c, 0x02 28 ], [0x5b4b, 0x03], [ 29 0x45f9, 0x04 30 ], [0x40ce, 0x05], [ 31 0x4f97, 0x06 32 ], [0x4aa0, 0x07], [ 33 0x77c4, 0x08 34 ], [0x72f3, 0x09], [ 35 0x7daa, 0x0a 36 ], [0x789d, 0x0b], [ 37 0x662f, 0x0c 38 ], [0x6318, 0x0d], [ 39 0x6c41, 0x0e 40 ], [0x6976, 0x0f], [ 41 0x1689, 0x10 42 ], [0x13be, 0x11], [ 43 0x1ce7, 0x12 44 ], [0x19d0, 0x13], [ 45 0x0762, 0x14 46 ], [0x0255, 0x15], [ 47 0x0d0c, 0x16 48 ], [0x083b, 0x17], [ 49 0x355f, 0x18 50 ], [0x3068, 0x19], [ 51 0x3f31, 0x1a 52 ], [0x3a06, 0x1b], [0x24b4, 0x1c], [0x2183, 0x1d], [0x2eda, 0x1e], [0x2bed, 0x1f],]; 53 54 /** 55 * Offset i holds the number of 1 bits in the binary representation of i. 56 * 57 * @var array 58 */ 59 protected static int[] bitsSetInHalfByte = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]; 60 61 /** 62 * Error correction level. 63 * 64 * @var ErrorCorrectionLevel 65 */ 66 protected ErrorCorrectionLevel ecLevel; 67 /** 68 * Data mask. 69 * 70 * @var integer 71 */ 72 protected int dataMask; 73 74 this(int formatInfo) 75 { 76 this.ecLevel = cast(ErrorCorrectionLevel)((formatInfo >> 3) & 0x3); 77 this.dataMask = formatInfo & 0x7; 78 } 79 80 /** 81 * Checks how many bits are different between two integers. 82 * 83 * @param integer a 84 * @param integer b 85 * @return integer 86 */ 87 public static int numBitsDiffering(int a, int b) 88 { 89 a ^= b; 90 return (bitsSetInHalfByte[a & 0xf] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 91 4) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 92 8) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 93 12) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 94 16) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 95 20) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 96 24) & 0xf)] + bitsSetInHalfByte[(BitUtils.unsignedRightShift(a, 28) & 0xf)]); 97 } 98 99 /** 100 * Decodes format information. 101 * 102 * @param integer $maskedFormatInfo1 103 * @param integer $maskedFormatInfo2 104 * @return FormatInformation|null 105 */ 106 public static FormatInformation decodeFormatInformation(int maskedFormatInfo1, 107 int maskedFormatInfo2) 108 { 109 auto formatInfo = doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2); 110 if (formatInfo !is null) 111 { 112 return formatInfo; 113 } 114 // Should return null, but, some QR codes apparently do not mask this 115 // info. Try again by actually masking the pattern first. 116 return doDecodeFormatInformation(maskedFormatInfo1 ^ FORMAT_INFO_MASK_QR, 117 maskedFormatInfo2 ^ FORMAT_INFO_MASK_QR); 118 } 119 120 /** 121 * Internal method for decoding format information. 122 * 123 * @param integer $maskedFormatInfo1 124 * @param integer $maskedFormatInfo2 125 * @return FormatInformation|null 126 */ 127 protected static FormatInformation doDecodeFormatInformation(int maskedFormatInfo1, 128 int maskedFormatInfo2) 129 { 130 int bestDifference = int.max; 131 int bestFormatInfo = 0; 132 foreach (decodeInfo; formatInfoDecodeLookup) 133 { 134 auto targetInfo = decodeInfo[0]; 135 if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2) 136 { 137 // Found an exact match 138 return new FormatInformation(decodeInfo[1]); 139 } 140 auto bitsDifference = numBitsDiffering(maskedFormatInfo1, targetInfo); 141 if (bitsDifference < bestDifference) 142 { 143 bestFormatInfo = decodeInfo[1]; 144 bestDifference = bitsDifference; 145 } 146 if (maskedFormatInfo1 != maskedFormatInfo2) 147 { 148 // Also try the other option 149 bitsDifference = numBitsDiffering(maskedFormatInfo2, targetInfo); 150 if (bitsDifference < bestDifference) 151 { 152 bestFormatInfo = decodeInfo[1]; 153 bestDifference = bitsDifference; 154 } 155 } 156 } 157 // Hamming distance of the 32 masked codes is 7, by construction, so 158 // <= 3 bits differing means we found a match. 159 if (bestDifference <= 3) 160 { 161 return new FormatInformation(bestFormatInfo); 162 } 163 return null; 164 } 165 166 /** 167 * Gets the error correction level. 168 * 169 * @return ErrorCorrectionLevel 170 */ 171 public ErrorCorrectionLevel getErrorCorrectionLevel() 172 { 173 return this.ecLevel; 174 } 175 /** 176 * Gets the data mask. 177 * 178 * @return integer 179 */ 180 public int getDataMask() 181 { 182 return this.dataMask; 183 } 184 /** 185 * Hashes the code of the EC level. 186 * 187 * @return integer 188 */ 189 public int hashCode() 190 { 191 return (this.ecLevel << 3) | this.dataMask; 192 } 193 /** 194 * Verifies if this instance equals another one. 195 * 196 * @param mixed $other 197 * @return boolean 198 */ 199 public bool equals(FormatInformation other) 200 { 201 if (typeid(FormatInformation) != typeid(other)) 202 { 203 return false; 204 } 205 return (ecLevel == other.getErrorCorrectionLevel() && dataMask == other.getDataMask()); 206 } 207 }