Skip to content

Commit

Permalink
Merge pull request #66 from VincentChalnot/fix/bigint-dependency
Browse files Browse the repository at this point in the history
Fixing Hash, removing need for BigInteger implementation
  • Loading branch information
jenssegers authored Mar 8, 2023
2 parents dfe3cdc + be8172f commit a23404b
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 31 deletions.
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
],
"require": {
"php": "^7.1|^8.0",
"intervention/image": "^2.4",
"phpseclib/phpseclib": "^3.0"
"intervention/image": "^2.4"
},
"require-dev": {
"phpunit/phpunit": "^7|^8|^9",
Expand Down
115 changes: 86 additions & 29 deletions src/Hash.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,140 @@
namespace Jenssegers\ImageHash;

use JsonSerializable;
use phpseclib3\Math\BigInteger;

class Hash implements JsonSerializable
{
/**
* @var BigInteger
* A string containing zeros and ones
*
* @var string
*/
protected $value;
protected $binaryValue;

private function __construct(BigInteger $value)
{
$this->value = $value;
}
/**
* Hash will be split in several integers if longer than PHP_INT_SIZE
*
* @var int[]|null
*/
protected $integers = null;

public static function fromHex(string $hex): self
/**
* @param string $binaryValue
*/
private function __construct(string $binaryValue)
{
return new self(new BigInteger($hex, 16));
$this->binaryValue = $binaryValue;
}

/**
* Create a hash from an array of bits or a string containing a binary representation of the hash
*
* @param string|array $bits
*
* @return self
*/
public static function fromBits($bits): self
{
if (is_array($bits)) {
if (\is_array($bits)) {
$bits = implode('', $bits);
}

return new self(new BigInteger($bits, 2));
}

public static function fromInt(int $int): self
{
return new self(new BigInteger($int, 10));
return new self($bits);
}

/**
* Use integers representation and concatenate their hexadecimal representation
*
* @return string
*/
public function toHex(): string
{
return $this->value->toHex();
}
if (\extension_loaded('gmp')) {
$gmp = gmp_init('0b'.$this->binaryValue);

public function toBytes(): string
{
return $this->value->toBytes();
return bin2hex(gmp_export($gmp));
}

return implode(
'',
array_map(
static function (int $int) {
return dechex($int);
},
$this->getIntegers()
)
);
}

public function toBits(): string
{
return $this->value->toBits();
return $this->binaryValue;
}

public function toInt(): int
/**
* Used to compute hexadecimal value and can be used to store the hash in database as an integer
*
* @return int[]
*/
public function getIntegers(): array
{
return hexdec($this->toHex());
if (null !== $this->integers) {
return $this->integers;
}

$maxIntSize = PHP_INT_SIZE * 8; // 8 bytes (a byte is 8 bits)

// Fixing binary if it doesn't fit an exact multiple of max int size
$fixedSizeBinary = str_pad(
$this->binaryValue,
((int) ceil(\strlen($this->binaryValue) / $maxIntSize)) * $maxIntSize, // Is there a better way?
'0',
STR_PAD_LEFT
);

$this->integers = [];
foreach (str_split($fixedSizeBinary, $maxIntSize) as $split) {
$sign = $split[0]; // Extract sign
$int = bindec(substr($split, 1)); // Convert to decimal without first bit
$int |= ((bool) $sign) << ($maxIntSize - 1); // Reapply last bit with bitwise operation
$this->integers[] = $int;
}

return $this->integers;
}

/**
* Super simple distance computation algorithm, we don't need anything else
*
* @param Hash $hash
*
* @return int
*/
public function distance(Hash $hash): int
{
if (extension_loaded('gmp')) {
return gmp_hamdist('0x' . $this->toHex(), '0x' . $hash->toHex());
if (\extension_loaded('gmp')) {
return gmp_hamdist('0b'.$this->toBits(), '0b'.$hash->toBits());
}

$bits1 = $this->toBits();
$bits2 = $hash->toBits();
$length = max(strlen($bits1), strlen($bits2));
$length = max(\strlen($bits1), \strlen($bits2));

// Add leading zeros so the bit strings are the same length.
$bits1 = str_pad($bits1, $length, '0', STR_PAD_LEFT);
$bits2 = str_pad($bits2, $length, '0', STR_PAD_LEFT);

return count(array_diff_assoc(str_split($bits1), str_split($bits2)));
return \count(array_diff_assoc(str_split($bits1), str_split($bits2)));
}

/**
* @param Hash $hash
*
* @return bool
*/
public function equals(Hash $hash): bool
{
return $this->toHex() === $hash->toHex();
return ltrim($this->binaryValue, '0') === ltrim($hash->binaryValue, '0');
}

public function __toString(): string
Expand Down

0 comments on commit a23404b

Please sign in to comment.