ecLevel = new ErrorCorrectionLevel(($formatInfo >> 3) & 0x3); $this->dataMask = $formatInfo & 0x7; } /** * Checks how many bits are different between two integers. * * @param integer $a * @param integer $b * @return integer */ public static function numBitsDiffering($a, $b) { $a ^= $b; return ( self::$bitsSetInHalfByte[$a & 0xf] + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 4) & 0xf)] + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 8) & 0xf)] + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 12) & 0xf)] + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 16) & 0xf)] + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 20) & 0xf)] + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 24) & 0xf)] + self::$bitsSetInHalfByte[(BitUtils::unsignedRightShift($a, 28) & 0xf)] ); } /** * Decodes format information. * * @param integer $maskedFormatInfo1 * @param integer $maskedFormatInfo2 * @return FormatInformation|null */ public static function decodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2) { $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); if ($formatInfo !== null) { 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. * * @param integer $maskedFormatInfo1 * @param integer $maskedFormatInfo2 * @return FormatInformation|null */ protected static function doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2) { $bestDifference = PHP_INT_MAX; $bestFormatInfo = 0; foreach (self::$formatInfoDecodeLookup 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; } /** * Gets the error correction level. * * @return ErrorCorrectionLevel */ public function getErrorCorrectionLevel() { return $this->ecLevel; } /** * Gets the data mask. * * @return integer */ public function getDataMask() { return $this->dataMask; } /** * Hashes the code of the EC level. * * @return integer */ public function hashCode() { return ($this->ecLevel->get() << 3) | $this->dataMask; } /** * Verifies if this instance equals another one. * * @param mixed $other * @return boolean */ public function equals($other) { if (!$other instanceof self) { return false; } return ( $this->ecLevel->get() === $other->getErrorCorrectionLevel()->get() && $this->dataMask === $other->getDataMask() ); } }