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'); } // Novo pipeline de processamento mais completo $this->detectAndCropDocument() ->convertToGrayscale() ->enhanceContrast() ->sharpenImage() ->removeNoise() ->adjustBrightnessContrast() ->normalizeBackground() ->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 convertToGrayscale() { if (!isset($this->processedImage)) { $this->processedImage = $this->srcImage; } imagefilter($this->processedImage, IMG_FILTER_GRAYSCALE); return $this; } private function enhanceContrast() { // Aplica equalização de histograma adaptativa $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); // Divide a imagem em blocos e aplica equalização local $blockSize = 32; for ($x = 0; $x < $width; $x += $blockSize) { for ($y = 0; $y < $height; $y += $blockSize) { $this->equalizeHistogramBlock($x, $y, min($blockSize, $width - $x), min($blockSize, $height - $y)); } } return $this; } private function equalizeHistogramBlock($startX, $startY, $blockWidth, $blockHeight) { $histogram = array_fill(0, 256, 0); $total = 0; // Calcula histograma do bloco for ($x = $startX; $x < $startX + $blockWidth; $x++) { for ($y = $startY; $y < $startY + $blockHeight; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); $gray = $colors['red']; // Já está em escala de cinza $histogram[$gray]++; $total++; } } // Calcula LUT de equalização $lut = array(); $sum = 0; for ($i = 0; $i < 256; $i++) { $sum += $histogram[$i]; $lut[$i] = round(($sum * 255) / $total); } // Aplica equalização for ($x = $startX; $x < $startX + $blockWidth; $x++) { for ($y = $startY; $y < $startY + $blockHeight; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); $newGray = $lut[$colors['red']]; $color = imagecolorallocate($this->processedImage, $newGray, $newGray, $newGray); imagesetpixel($this->processedImage, $x, $y, $color); } } } private function sharpenImage() { // Matriz de convolução para sharpening $sharpenMatrix = [ [-1, -1, -1], [-1, 9, -1], [-1, -1, -1] ]; $this->applyConvolution($sharpenMatrix); return $this; } private function removeNoise() { // Aplica filtro mediano $temp = imagecreatetruecolor(imagesx($this->processedImage), imagesy($this->processedImage)); imagecopy($temp, $this->processedImage, 0, 0, 0, 0, imagesx($this->processedImage), imagesy($this->processedImage)); for ($x = 1; $x < imagesx($this->processedImage) - 1; $x++) { for ($y = 1; $y < imagesy($this->processedImage) - 1; $y++) { $values = array(); // Coleta valores dos pixels vizinhos for ($i = -1; $i <= 1; $i++) { for ($j = -1; $j <= 1; $j++) { $rgb = imagecolorat($temp, $x + $i, $y + $j); $colors = imagecolorsforindex($temp, $rgb); $values[] = $colors['red']; } } sort($values); $median = $values[4]; // Valor mediano $color = imagecolorallocate($this->processedImage, $median, $median, $median); imagesetpixel($this->processedImage, $x, $y, $color); } } imagedestroy($temp); return $this; } private function adjustBrightnessContrast() { // Ajusta brilho automaticamente imagefilter($this->processedImage, IMG_FILTER_BRIGHTNESS, 10); imagefilter($this->processedImage, IMG_FILTER_CONTRAST, -10); return $this; } private function normalizeBackground() { $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); // Detecta cor do fundo (assume que os cantos são fundo) $cornerColors = array(); $cornerColors[] = imagecolorat($this->processedImage, 0, 0); $cornerColors[] = imagecolorat($this->processedImage, $width-1, 0); $cornerColors[] = imagecolorat($this->processedImage, 0, $height-1); $cornerColors[] = imagecolorat($this->processedImage, $width-1, $height-1); // Calcula cor média do fundo $avgBackground = 255; // Assumimos fundo branco // Normaliza fundo for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); if ($colors['red'] > $avgBackground - 30) { // Se for próximo do fundo $color = imagecolorallocate($this->processedImage, 255, 255, 255); imagesetpixel($this->processedImage, $x, $y, $color); } } } return $this; } 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; } }