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; } }