ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3); $this->dataMask = $formatInfo & 0x7; } /** * Checks how many bits are different between two integers. */ public static function numBitsDiffering(int $a, int $b) : int { $a ^= $b; return ( self::BITS_SET_IN_HALF_BYTE[$a & 0xf] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)] ); } /** * Decodes format information. */ public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self { $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); if (null !== $formatInfo) { return $formatInfo; } // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the // pattern first. return self::doDecodeFormatInformation( $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR, $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR ); } /** * Internal method for decoding format information. */ private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self { $bestDifference = PHP_INT_MAX; $bestFormatInfo = 0; foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) { $targetInfo = $decodeInfo[0]; if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) { // Found an exact match return new self($decodeInfo[1]); } $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo); if ($bitsDifference < $bestDifference) { $bestFormatInfo = $decodeInfo[1]; $bestDifference = $bitsDifference; } if ($maskedFormatInfo1 !== $maskedFormatInfo2) { // Also try the other option $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo); if ($bitsDifference < $bestDifference) { $bestFormatInfo = $decodeInfo[1]; $bestDifference = $bitsDifference; } } } // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match. if ($bestDifference <= 3) { return new self($bestFormatInfo); } return null; } /** * Returns the error correction level. */ public function getErrorCorrectionLevel() : ErrorCorrectionLevel { return $this->ecLevel; } /** * Returns the data mask. */ public function getDataMask() : int { return $this->dataMask; } /** * Hashes the code of the EC level. */ public function hashCode() : int { return ($this->ecLevel->getBits() << 3) | $this->dataMask; } /** * Verifies if this instance equals another one. */ public function equals(self $other) : bool { return ( $this->ecLevel === $other->ecLevel && $this->dataMask === $other->dataMask ); } }