commit ba43d87019fc661ae74b3de66181a0943a62d393 Author: travail Date: Fri Jan 9 11:28:30 2015 +0900 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19982ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1daf238 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "travail/image-perceptualhash", + "description": "travail/image-perceptualhash", + "license": "PHP-3.0", + "minimum-stability": "dev", + "autoload": { + "psr-4": { + "Image\\": "lib" + } + }, + "require": { + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + } +} diff --git a/lib/PerceptualHash.php b/lib/PerceptualHash.php new file mode 100644 index 0000000..f0abaf6 --- /dev/null +++ b/lib/PerceptualHash.php @@ -0,0 +1,69 @@ +algorithm = $algorithm ?: new AverageHash; + $this->data = is_resource($file) ? $file : $this->load($file); + } + + public function hash() + { + return $this->algorithm->calculate($this->data); + } + + public function compare($file) + { + $algorithm_class = get_class($this->algorithm); + $objective = new self($file, new $algorithm_class); + + return self::hammingDistance($this->hash(), $objective->hash()); + } + + public static function hammingDistance($hash1, $hash2) + { + if (!is_string($hash1) || !is_string($hash2)) { + throw new Exception(); + } + + if (strlen($hash1) !== strlen($hash2)) { + throw new Exception(); + } + + $diff = 0; + $split_hash1 = str_split($hash1); + $split_hash2 = str_split($hash2); + for ($i = 0; $i < count($split_hash1); $i++) { + if ($split_hash1[$i] !== $split_hash2[$i]) { + $diff++; + } + } + + return $diff; + } + + protected function load($file) + { + if (!file_exists($file)) { + throw new Exception("No such a file $file"); + } + + try { + return imagecreatefromstring(file_get_contents($file)); + } catch (Exception $e) { + throw $e; + } + } +} diff --git a/lib/PerceptualHash/Algorithm.php b/lib/PerceptualHash/Algorithm.php new file mode 100644 index 0000000..1db417e --- /dev/null +++ b/lib/PerceptualHash/Algorithm.php @@ -0,0 +1,14 @@ + $average ? 1 : 0; + $one = $one << 1; + } + + $hex = ''; + foreach (str_split($binary, 4) as $binary) { + $hex .= dechex(bindec($binary)); + } + + return $hex; + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..c3adb6f --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + + + tests + + + + diff --git a/tests/AverageHashTest.php b/tests/AverageHashTest.php new file mode 100644 index 0000000..be59d38 --- /dev/null +++ b/tests/AverageHashTest.php @@ -0,0 +1,40 @@ +path_inuo1 = __DIR__ . '/images/inuo1.jpg'; + $this->path_inuo2 = __DIR__ . '/images/inuo2.jpg'; + $this->hash_inuo1 = 'fdd7a9d1c383c1ff'; + $this->hash_inuo2 = 'f7f30200c3c3c3ff'; + } + + public function testHash() + { + $ph = new PerceptualHash($this->path_inuo1); + $hash = $ph->hash(); + + $this->assertEquals(16, strlen($hash)); + $this->assertEquals($this->hash_inuo1, $hash); + } + + public function testCompareDifferentImage() + { + $ph = new PerceptualHash($this->path_inuo1); + $diff = $ph->compare($this->path_inuo2); + + $this->assertEquals(9, $diff); + + } + + public function testCompareSameImage() + { + $ph = new PerceptualHash($this->path_inuo1); + $diff = $ph->compare($this->path_inuo1); + + $this->assertEquals(0, $diff); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..6c8c4f5 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,3 @@ +