phash.php
1 <?php 2 3 class Phash { 4 const IMG_SIZE = 32; 5 6 // Precalculates DCT constants matrix and its transpose 7 private static function dct_matrices() { 8 $N = self::IMG_SIZE; 9 10 static $ret = null; 11 12 if ($ret !== null) { 13 return $ret; 14 } 15 16 $dct = []; 17 18 $c0 = sqrt(1.0 / $N); 19 $c1 = sqrt(2.0 / $N); 20 21 $hpn = M_PI / 2 / $N; 22 23 for ($y = 0; $y < $N; ++$y) { 24 $row = []; 25 26 if ($y === 0) { 27 $c = $c0; 28 } 29 else { 30 $c = $c1; 31 } 32 33 for ($x = 0; $x < $N; ++$x) { 34 $row[] = $c * cos($hpn * $y * (2 * $x + 1)); 35 } 36 37 $dct[] = $row; 38 } 39 40 $dct_t = array_map(null, ...$dct); 41 42 $ret = [ $dct, $dct_t ]; 43 44 return $ret; 45 } 46 47 private static function mat_mul($mat1, $mat2) { 48 $N = self::IMG_SIZE; 49 50 $ret = []; 51 52 for ($i = 0; $i < $N; $i++) { 53 $ret[$i] = []; 54 55 for ($j = 0; $j < $N; $j++) { 56 $ret[$i][$j] = 0; 57 58 for ($k = 0; $k < $N; $k++) { 59 $ret[$i][$j] += $mat1[$i][$k] * $mat2[$k][$j]; 60 } 61 } 62 } 63 64 return $ret; 65 } 66 67 private static function median($array) { 68 if (!$array) { 69 return 0; 70 } 71 72 sort($array); 73 74 $count = count($array); 75 76 $mid = (int)($count / 2); 77 78 if ($count & 1) { 79 return $array[$mid]; 80 } 81 else { 82 return ($array[$mid - 1] + $array[$mid]) / 2; 83 } 84 } 85 86 public static function hash_distance($hash1, $hash2) { 87 $counts = [0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4]; 88 89 $res = 0; 90 91 for ($i = 0; $i < 16; $i++) { 92 if ($hash1[$i] != $hash2[$i]) { 93 $res += $counts[hexdec($hash1[$i]) ^ hexdec($hash2[$i])]; 94 } 95 } 96 97 return $res; 98 } 99 100 // $input_image is a GD image instance 101 public static function hash($input_image, $width, $height) { 102 if (!$input_image || $width < 1 || $height < 1) { 103 return false; 104 } 105 106 $N = self::IMG_SIZE; 107 108 $img = imagecreatetruecolor($N, $N); 109 110 if (!$img) { 111 return false; 112 } 113 114 $_ret = imagecopyresampled($img, $input_image, 0, 0, 0, 0, $N, $N, $width, $height); 115 116 if (!$_ret) { 117 return false; 118 } 119 120 imagefilter($img, IMG_FILTER_GRAYSCALE); 121 122 $colors = []; 123 124 for ($y = 0; $y < $N; $y++){ 125 $row = []; 126 127 for ($x = 0; $x < $N; $x++){ 128 $row[] = imagecolorat($img, $x, $y) & 0xFF; 129 } 130 131 $colors[] = $row; 132 } 133 134 imagedestroy($img); 135 136 list($dct, $dct_t) = self::dct_matrices(); 137 138 $dct_img = self::mat_mul(self::mat_mul($dct, $colors), $dct_t); 139 140 $subsec = []; 141 142 for ($y = 1; $y < 9; ++$y) { 143 for ($x = 1; $x < 9; ++$x) { 144 $subsec[] = $dct_img[$y][$x]; 145 } 146 } 147 148 $median = self::median($subsec); 149 150 $hash = 0; 151 152 for ($i = 0; $i < 64; $i++, $hash <<= 1) { 153 if ($subsec[$i] > $median) { 154 $hash |= 0x01; 155 } 156 } 157 158 return sprintf("%016x", $hash); 159 } 160 161 public static function hash_file($image_path, &$err = null) { 162 if (!$image_path) { 163 return false; 164 } 165 166 $size = getimagesize($image_path); 167 168 if (!$size) { 169 $err = "Couldn't get file information"; 170 return false; 171 } 172 173 $type = $size[2]; 174 175 if ($type === IMAGETYPE_PNG) { 176 $img = imagecreatefrompng($image_path); 177 } 178 else if ($type === IMAGETYPE_JPEG) { 179 $img = imagecreatefromjpeg($image_path); 180 } 181 else if ($type === IMAGETYPE_GIF) { 182 $img = imagecreatefromgif($image_path); 183 } 184 else { 185 $err = "Invalid image"; 186 return false; 187 } 188 189 $width = $size[0]; 190 $height = $size[1]; 191 192 $hash = self::hash($img, $width, $height); 193 194 if ($hash === false) { 195 $err = "Couldn't process the image"; 196 return false; 197 } 198 199 return $hash; 200 } 201 }