outputPath = $outputPath; // Detecta o tipo da imagem e carrega $imageInfo = getimagesize($imagePath); if ($imageInfo === false) { throw new Exception('Failed to load image'); } switch ($imageInfo[2]) { case IMAGETYPE_JPEG: $this->srcImage = imagecreatefromjpeg($imagePath); break; case IMAGETYPE_PNG: $this->srcImage = imagecreatefrompng($imagePath); break; default: throw new Exception('Unsupported image type'); } if ($this->srcImage === false) { throw new Exception('Failed to create image resource'); } // Simplifica o pipeline para focar apenas na detecção e recorte $this->detectAndCropDocument() ->saveResult(); return $this->outputPath; } private function detectAndCropDocument() { $width = imagesx($this->srcImage); $height = imagesy($this->srcImage); // Cria imagem temporária para detecção $temp = imagecreatetruecolor($width, $height); imagecopy($temp, $this->srcImage, 0, 0, 0, 0, $width, $height); // Converte para escala de cinza imagefilter($temp, IMG_FILTER_GRAYSCALE); // Aumenta o contraste para melhor detecção imagefilter($temp, IMG_FILTER_CONTRAST, -20); imagefilter($temp, IMG_FILTER_BRIGHTNESS, 20); // Detecta bordas do documento $edges = $this->findDocumentBorders($temp); if ($edges) { // Recorta e corrige perspectiva $this->processedImage = $this->cropAndTransform($this->srcImage, $edges); } else { $this->processedImage = $this->srcImage; } imagedestroy($temp); return $this; } private function findDocumentBorders($img) { $width = imagesx($img); $height = imagesy($img); $points = []; // Procura pontos de borda for ($x = 0; $x < $width; $x += 10) { for ($y = 0; $y < $height; $y += 10) { $rgb = imagecolorat($img, $x, $y); $colors = imagecolorsforindex($img, $rgb); // Detecta mudanças bruscas de intensidade if ($this->isEdgePoint($img, $x, $y)) { $points[] = [$x, $y]; } } } // Encontra os 4 cantos do documento if (count($points) >= 4) { return $this->findCornerPoints($points); } return null; } private function isEdgePoint($img, $x, $y) { $threshold = 30; // Verifica pixels vizinhos $center = $this->getPixelIntensity($img, $x, $y); $left = $this->getPixelIntensity($img, $x - 1, $y); $right = $this->getPixelIntensity($img, $x + 1, $y); $top = $this->getPixelIntensity($img, $x, $y - 1); $bottom = $this->getPixelIntensity($img, $x, $y + 1); return (abs($center - $left) > $threshold || abs($center - $right) > $threshold || abs($center - $top) > $threshold || abs($center - $bottom) > $threshold); } private function getPixelIntensity($img, $x, $y) { if ($x < 0 || $y < 0 || $x >= imagesx($img) || $y >= imagesy($img)) { return 0; } $rgb = imagecolorat($img, $x, $y); $colors = imagecolorsforindex($img, $rgb); return ($colors['red'] + $colors['green'] + $colors['blue']) / 3; } private function findCornerPoints($points) { // Encontra os pontos mais extremos $topLeft = null; $topRight = null; $bottomLeft = null; $bottomRight = null; $minSum = PHP_FLOAT_MAX; $maxSum = PHP_FLOAT_MIN; $minDiff = PHP_FLOAT_MAX; $maxDiff = PHP_FLOAT_MIN; foreach ($points as $point) { $sum = $point[0] + $point[1]; $diff = $point[0] - $point[1]; if ($sum < $minSum) { $minSum = $sum; $topLeft = $point; } if ($sum > $maxSum) { $maxSum = $sum; $bottomRight = $point; } if ($diff < $minDiff) { $minDiff = $diff; $bottomLeft = $point; } if ($diff > $maxDiff) { $maxDiff = $diff; $topRight = $point; } } if ($topLeft && $topRight && $bottomLeft && $bottomRight) { return [$topLeft, $topRight, $bottomRight, $bottomLeft]; } return null; } private function cropAndTransform($img, $corners) { $width = abs($corners[1][0] - $corners[0][0]); $height = abs($corners[3][1] - $corners[0][1]); $dest = imagecreatetruecolor($width, $height); // Preserva transparência imagealphablending($dest, false); imagesavealpha($dest, true); // Define pontos de destino $dest_points = [ 0, 0, $width - 1, 0, $width - 1, $height - 1, 0, $height - 1 ]; // Define pontos de origem $src_points = []; foreach ($corners as $corner) { $src_points[] = $corner[0]; $src_points[] = $corner[1]; } // Aplica transformação perspectiva imagecopyresampled( $dest, $img, 0, 0, min($src_points[0], $src_points[6]), min($src_points[1], $src_points[3]), $width, $height, max($src_points[2], $src_points[4]) - min($src_points[0], $src_points[6]), max($src_points[5], $src_points[7]) - min($src_points[1], $src_points[3]) ); return $dest; } private function saveResult() { if (!isset($this->processedImage)) { throw new Exception('No processed image to save'); } $extension = strtolower(pathinfo($this->outputPath, PATHINFO_EXTENSION)); switch ($extension) { case 'jpg': case 'jpeg': imagejpeg($this->processedImage, $this->outputPath, 90); break; case 'png': imagepng($this->processedImage, $this->outputPath, 9); break; default: throw new Exception('Unsupported output image type'); } if (is_resource($this->srcImage)) { imagedestroy($this->srcImage); } if (is_resource($this->processedImage)) { imagedestroy($this->processedImage); } return $this; } }