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 em escala de cinza $gray = imagecreatetruecolor($width, $height); imagecopy($gray, $this->srcImage, 0, 0, 0, 0, $width, $height); imagefilter($gray, IMG_FILTER_GRAYSCALE); // Aplica threshold adaptativo para binarização $threshold = $this->getAdaptiveThreshold($gray); $binary = imagecreatetruecolor($width, $height); for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $rgb = imagecolorat($gray, $x, $y); $colors = imagecolorsforindex($gray, $rgb); $value = ($colors['red'] + $colors['green'] + $colors['blue']) / 3; if ($value > $threshold) { imagesetpixel($binary, $x, $y, 0xFFFFFF); } else { imagesetpixel($binary, $x, $y, 0x000000); } } } // Encontra os contornos do documento $bounds = $this->findDocumentBounds($binary); if ($bounds) { // Adiciona margem de segurança $margin = 10; $cropX = max(0, $bounds['left'] - $margin); $cropY = max(0, $bounds['top'] - $margin); $cropWidth = min($width - $cropX, $bounds['right'] - $bounds['left'] + 2 * $margin); 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; } }