Initial commit

This commit is contained in:
travail 2015-01-09 11:28:30 +09:00
commit ba43d87019
10 changed files with 216 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
composer.lock
vendor

17
composer.json Normal file
View File

@ -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.*"
}
}

69
lib/PerceptualHash.php Normal file
View File

@ -0,0 +1,69 @@
<?php
namespace Image;
use Exception;
use Image\PerceptualHash\Algorithm;
use Image\PerceptualHash\Algorithm\AverageHash;
class PerceptualHash {
/**
@var Algorithm The hasing algorithm
*/
protected $algorithm;
protected $data;
public function __construct($file, Algorithm $algorithm = null)
{
$this->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;
}
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Image\PerceptualHash;
interface Algorithm
{
/**
* Calculate the hash
*
* @param resource $resource
* @return string
*/
public function calculate($resource);
}

View File

@ -0,0 +1,50 @@
<?php
namespace Image\PerceptualHash\Algorithm;
use Image\PerceptualHash\Algorithm;
class AverageHash implements Algorithm
{
const SIZE = 8;
/**
* {@inheritDoc}
*/
public function calculate($resource)
{
// Resize
$resized = imagecreatetruecolor(static::SIZE, static::SIZE);
imagecopyresampled(
$resized, $resource, 0, 0, 0, 0,
static::SIZE, static::SIZE, imagesx($resource), imagesy($resource));
// Create an array of gray-scale pixel value
$pixels = array();
for ($y = 0; $y < static::SIZE; $y++) {
for ($x = 0; $x < static::SIZE; $x++) {
$rgb = imagecolorsforindex($resized, imagecolorat($resized, $x, $y));
$pixels[] = floor(($rgb['red'] + $rgb['green'] + $rgb['blue']) / 3);
}
}
imagedestroy($resized);
// Calculate the average pixel value
$average = floor(array_sum($pixels) / count($pixels));
$binary = '';
$one = 1;
foreach ($pixels as $pixel) {
$binary .= $pixel > $average ? 1 : 0;
$one = $one << 1;
}
$hex = '';
foreach (str_split($binary, 4) as $binary) {
$hex .= dechex(bindec($binary));
}
return $hex;
}
}

21
phpunit.xml.dist Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="Test! Test! Test!">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
</phpunit>

40
tests/AverageHashTest.php Normal file
View File

@ -0,0 +1,40 @@
<?php
use Image\PerceptualHash;
class AverageHashTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->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);
}
}

3
tests/bootstrap.php Normal file
View File

@ -0,0 +1,3 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';

BIN
tests/images/inuo1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
tests/images/inuo2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB