101 lines
2.7 KiB
PHP
101 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace App\Helpers\FirstParty\ImageHash;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Jenssegers\ImageHash\ImageHash;
|
|
use Jenssegers\ImageHash\Implementations\DifferenceHash;
|
|
|
|
class ImageHashService
|
|
{
|
|
private ImageHash $hasher;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->hasher = new ImageHash(new DifferenceHash);
|
|
}
|
|
|
|
public function generateHashFromUrl(string $url): ?string
|
|
{
|
|
try {
|
|
$response = Http::timeout(30)->get($url);
|
|
|
|
if (! $response->successful()) {
|
|
Log::warning("Failed to download image from URL: {$url}");
|
|
|
|
return null;
|
|
}
|
|
|
|
$imageData = $response->body();
|
|
|
|
return $this->generateHashFromData($imageData);
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error generating hash from URL {$url}: ".$e->getMessage());
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public function generateHashFromData(string $imageData): ?string
|
|
{
|
|
try {
|
|
$tempFile = tempnam(sys_get_temp_dir(), 'imagehash_');
|
|
file_put_contents($tempFile, $imageData);
|
|
|
|
$hash = $this->hasher->hash($tempFile);
|
|
unlink($tempFile);
|
|
|
|
return $hash->toHex();
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Error generating hash from image data: '.$e->getMessage());
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public function calculateHammingDistance(string $hash1, string $hash2): int
|
|
{
|
|
// Validate hashes are not empty
|
|
if (empty($hash1) || empty($hash2)) {
|
|
return PHP_INT_MAX; // Return max distance for invalid hashes
|
|
}
|
|
|
|
// Pad shorter hash with zeros to make them equal length
|
|
$maxLength = max(strlen($hash1), strlen($hash2));
|
|
$hash1 = str_pad($hash1, $maxLength, '0', STR_PAD_LEFT);
|
|
$hash2 = str_pad($hash2, $maxLength, '0', STR_PAD_LEFT);
|
|
|
|
$distance = 0;
|
|
for ($i = 0; $i < $maxLength; $i++) {
|
|
if ($hash1[$i] !== $hash2[$i]) {
|
|
$distance++;
|
|
}
|
|
}
|
|
|
|
return $distance;
|
|
}
|
|
|
|
public function areHashesSimilar(string $hash1, string $hash2, int $threshold = 5): bool
|
|
{
|
|
return $this->calculateHammingDistance($hash1, $hash2) <= $threshold;
|
|
}
|
|
|
|
public function findSimilarHashes(string $targetHash, array $hashes, int $threshold = 5): array
|
|
{
|
|
$similar = [];
|
|
|
|
foreach ($hashes as $id => $hash) {
|
|
if ($this->areHashesSimilar($targetHash, $hash, $threshold)) {
|
|
$similar[$id] = $this->calculateHammingDistance($targetHash, $hash);
|
|
}
|
|
}
|
|
|
|
asort($similar);
|
|
|
|
return $similar;
|
|
}
|
|
}
|