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'); } // Analisa qualidade da imagem e define parâmetros $this->analyzeImageQuality() ->determineOptimalParameters(); // Pipeline de processamento adaptativo $this->detectAndCropDocument() ->convertToGrayscale() ->applyAdaptiveEnhancements() ->saveResult(); return $this->outputPath; } private function detectAndCropDocument() { $width = imagesx($this->srcImage); $height = imagesy($this->srcImage); // Cria cópia para processamento de detecção $work = imagecreatetruecolor($width, $height); imagecopy($work, $this->srcImage, 0, 0, 0, 0, $width, $height); // Prepara imagem para detecção de bordas imagefilter($work, IMG_FILTER_GAUSSIAN_BLUR); imagefilter($work, IMG_FILTER_GRAYSCALE); imagefilter($work, IMG_FILTER_CONTRAST, -10); // Detecta bordas com Sobel mais sensível $edges = $this->detectEdgesSobel($work); // Melhora a detecção de contornos $contourPoints = $this->findSignificantContours($edges); // Encontra os cantos do documento $corners = $this->findCornerPoints($contourPoints); if ($corners && $this->isValidQuadrilateral($corners)) { // Aplica transformação perspectiva mantendo cores $this->processedImage = $this->cropAndTransformColor($this->srcImage, $corners); } else { $this->processedImage = $this->srcImage; } imagedestroy($work); imagedestroy($edges); return $this; } private function findSignificantContours($edges) { $width = imagesx($edges); $height = imagesy($edges); $contourPoints = []; $threshold = 200; // Ajuste de sensibilidade // Passo adaptativo baseado no tamanho da imagem $step = max(1, min(3, (int)($width * $height / 1000000))); for ($x = 0; $x < $width; $x += $step) { for ($y = 0; $y < $height; $y += $step) { $rgb = imagecolorat($edges, $x, $y); if ($rgb > $threshold) { $strength = $this->getEdgeStrength($edges, $x, $y); if ($strength > 100) { // Filtro de força da borda $contourPoints[] = [$x, $y]; } } } } return $contourPoints; } private function getEdgeStrength($img, $x, $y) { $sum = 0; $count = 0; // Analisa vizinhança 3x3 for ($i = -1; $i <= 1; $i++) { for ($j = -1; $j <= 1; $j++) { if ($x + $i >= 0 && $x + $i < imagesx($img) && $y + $j >= 0 && $y + $j < imagesy($img)) { $rgb = imagecolorat($img, $x + $i, $y + $j); $colors = imagecolorsforindex($img, $rgb); $sum += $colors['red']; $count++; } } } return $count > 0 ? $sum / $count : 0; } private function isValidQuadrilateral($corners) { if (count($corners) !== 4) return false; // Verifica se os cantos formam um quadrilátero razoável $minArea = 1000; // Área mínima em pixels $area = $this->calculateQuadArea($corners); // Verifica proporções $width = max(abs($corners[1][0] - $corners[0][0]), abs($corners[2][0] - $corners[3][0])); $height = max(abs($corners[3][1] - $corners[0][1]), abs($corners[2][1] - $corners[1][1])); $ratio = $width / $height; return $area > $minArea && $ratio > 0.2 && $ratio < 5; } private function calculateQuadArea($corners) { // Calcula área usando fórmula do trapézio $area = 0; $n = count($corners); for ($i = 0; $i < $n; $i++) { $j = ($i + 1) % $n; $area += $corners[$i][0] * $corners[$j][1]; $area -= $corners[$j][0] * $corners[$i][1]; } return abs($area / 2); } private function cropAndTransformColor($img, $corners) { // Calcula dimensões baseadas nos cantos detectados $width = max( abs($corners[1][0] - $corners[0][0]), abs($corners[2][0] - $corners[3][0]) ); $height = max( abs($corners[3][1] - $corners[0][1]), abs($corners[2][1] - $corners[1][1]) ); $dest = imagecreatetruecolor($width, $height); // Mantém transparência se existir imagealphablending($dest, false); imagesavealpha($dest, true); // Pontos de origem e destino para a transformação $src_points = []; foreach ($corners as $corner) { $src_points[] = $corner[0]; $src_points[] = $corner[1]; } $dst_points = [ 0, 0, $width - 1, 0, $width - 1, $height - 1, 0, $height - 1 ]; // Aplica transformação perspectiva mantendo cores if (function_exists('imagecreatetruecolor')) { $result = 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]) ); if (!$result) { return $img; // Fallback para imagem original se falhar } } return $dest; } private function detectEdgesSobel($image) { $width = imagesx($image); $height = imagesy($image); $edges = imagecreatetruecolor($width, $height); // Kernels Sobel $sobelX = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]; $sobelY = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]; for ($x = 1; $x < $width-1; $x++) { for ($y = 1; $y < $height-1; $y++) { $pixelX = $pixelY = 0; // Aplica kernels for ($i = -1; $i <= 1; $i++) { for ($j = -1; $j <= 1; $j++) { $rgb = imagecolorat($image, $x + $i, $y + $j); $colors = imagecolorsforindex($image, $rgb); $gray = $colors['red']; $pixelX += $gray * $sobelX[$i+1][$j+1]; $pixelY += $gray * $sobelY[$i+1][$j+1]; } } // Magnitude do gradiente $magnitude = min(255, sqrt($pixelX * $pixelX + $pixelY * $pixelY)); // Aplica threshold para reduzir ruído $magnitude = ($magnitude > 50) ? 255 : 0; $color = imagecolorallocate($edges, $magnitude, $magnitude, $magnitude); imagesetpixel($edges, $x, $y, $color); } } return $edges; } private function findDocumentBounds($binary) { $width = imagesx($binary); $height = imagesy($binary); $left = $width; $right = 0; $top = $height; $bottom = 0; // Encontra os limites do documento for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $rgb = imagecolorat($binary, $x, $y); if ($rgb == 0) { // Pixel preto $left = min($left, $x); $right = max($right, $x); $top = min($top, $y); $bottom = max($bottom, $y); } } } if ($left < $right && $top < $bottom) { return [ 'left' => $left, 'right' => $right, 'top' => $top, 'bottom' => $bottom ]; } 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() { $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); // Calcula histograma $histogram = array_fill(0, 256, 0); for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); $histogram[$colors['red']]++; } } // Encontra limites para stretch $min = 0; $max = 255; $pixelCount = $width * $height; $cutoffPercent = 0.005; // 0.5% $sum = 0; for ($i = 0; $i < 256; $i++) { $sum += $histogram[$i]; if ($sum >= $pixelCount * $cutoffPercent) { $min = $i; break; } } $sum = 0; for ($i = 255; $i >= 0; $i--) { $sum += $histogram[$i]; if ($sum >= $pixelCount * $cutoffPercent) { $max = $i; break; } } // Aplica contrast stretching for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); $gray = $colors['red']; // Normaliza valor $newGray = ($gray - $min) * (255 / ($max - $min)); $newGray = min(255, max(0, $newGray)); $color = imagecolorallocate($this->processedImage, $newGray, $newGray, $newGray); imagesetpixel($this->processedImage, $x, $y, $color); } } return $this; } private function sharpenImage() { $sharpenMatrix = [ [-1, -1, -1], [-1, 16, -1], [-1, -1, -1] ]; $this->applyConvolution($sharpenMatrix); // Ajuste fino pós-sharpening imagefilter($this->processedImage, IMG_FILTER_CONTRAST, -5); return $this; } private function applyConvolution($matrix) { if (!$this->processedImage) { return $this; } $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); $temp = imagecreatetruecolor($width, $height); if (!$temp) { return $this; } imagecopy($temp, $this->processedImage, 0, 0, 0, 0, $width, $height); $matrixSize = count($matrix); $halfSize = floor($matrixSize / 2); for ($x = $halfSize; $x < $width - $halfSize; $x++) { for ($y = $halfSize; $y < $height - $halfSize; $y++) { $r = $g = $b = 0; for ($i = -$halfSize; $i <= $halfSize; $i++) { for ($j = -$halfSize; $j <= $halfSize; $j++) { $rgb = imagecolorat($temp, $x + $i, $y + $j); $colors = imagecolorsforindex($temp, $rgb); $weight = $matrix[$i + $halfSize][$j + $halfSize]; $r += $colors['red'] * $weight; $g += $colors['green'] * $weight; $b += $colors['blue'] * $weight; } } $r = min(255, max(0, $r)); $g = min(255, max(0, $g)); $b = min(255, max(0, $b)); $color = imagecolorallocate($this->processedImage, $r, $g, $b); imagesetpixel($this->processedImage, $x, $y, $color); } } imagedestroy($temp); 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() { // Ajustes mais suaves imagefilter($this->processedImage, IMG_FILTER_BRIGHTNESS, 5); imagefilter($this->processedImage, IMG_FILTER_CONTRAST, -8); return $this; } private function normalizeBackground() { if (!$this->processedImage) { return $this; } $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); // Corrigir loop infinito na amostragem das bordas $samples = []; $borderSize = 10; // Amostra pixels das bordas com índice y corrigido for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $borderSize; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); $samples[] = $colors['red']; } // Corrigir incremento do y for ($y = $height - $borderSize; $y < $height; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); $samples[] = $colors['red']; } } $mean = array_sum($samples) / count($samples); $stdDev = sqrt(array_sum(array_map(function($x) use ($mean) { return pow($x - $mean, 2); }, $samples)) / count($samples)); // Normaliza fundo usando estatísticas $threshold = $mean - ($stdDev * 2.5); 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'] > $threshold) { $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; } private function getAdaptiveThreshold($image) { $width = imagesx($image); $height = imagesy($image); $blockSize = 40; // Tamanho do bloco para threshold adaptativo $C = 10; // Constante de ajuste // Cria imagem para o resultado $result = imagecreatetruecolor($width, $height); // Para cada bloco for ($x = 0; $x < $width; $x += $blockSize) { for ($y = 0; $y < $height; $y += $blockSize) { // Calcula média do bloco $sum = 0; $count = 0; $blockW = min($blockSize, $width - $x); $blockH = min($blockSize, $height - $y); for ($bx = 0; $bx < $blockW; $bx++) { for ($by = 0; $by < $blockH; $by++) { $rgb = imagecolorat($image, $x + $bx, $y + $by); $colors = imagecolorsforindex($image, $rgb); $gray = $colors['red']; // Já está em escala de cinza $sum += $gray; $count++; } } // Calcula threshold local $threshold = ($count > 0) ? ($sum / $count) - $C : 128; // Aplica threshold ao bloco for ($bx = 0; $bx < $blockW; $bx++) { for ($by = 0; $by < $blockH; $by++) { $rgb = imagecolorat($image, $x + $bx, $y + $by); $colors = imagecolorsforindex($image, $rgb); $gray = $colors['red']; if ($gray > $threshold) { $color = imagecolorallocate($result, 255, 255, 255); } else { $color = imagecolorallocate($result, 0, 0, 0); } imagesetpixel($result, $x + $bx, $color); } } } } return $threshold; } private function analyzeImageQuality() { if (!$this->srcImage) { $this->imageQualityMetrics = [ 'brightness' => 0, 'contrast' => 0, 'noise' => 0, 'blur' => 0 ]; return $this; } $width = imagesx($this->srcImage); $height = imagesy($this->srcImage); // Reduz a amostragem para análise mais rápida $sampleStep = max(1, (int)($width * $height / 100000)); $histogram = array_fill(0, 256, 0); $totalSamples = 0; // Amostragem reduzida para cálculo mais rápido for ($x = 0; $x < $width; $x += $sampleStep) { for ($y = 0; $y < $height; $y += $sampleStep) { $rgb = imagecolorat($this->srcImage, $x, $y); $colors = imagecolorsforindex($this->srcImage, $rgb); $gray = (int)(($colors['red'] + $colors['green'] + $colors['blue']) / 3); $histogram[$gray]++; $totalSamples++; } } $this->imageQualityMetrics = [ 'brightness' => $this->calculateAverageBrightness($histogram, $totalSamples), 'contrast' => $this->calculateImageContrast($histogram, $totalSamples), 'noise' => $this->estimateImageNoise(50), // Reduz número de amostras 'blur' => $this->estimateBlurLevel(100) // Reduz número de amostras ]; return $this; } private function determineOptimalParameters() { // Define parâmetros baseados nas métricas $this->processingParams = [ 'contrast_adjustment' => $this->calculateContrastAdjustment(), 'brightness_adjustment' => $this->calculateBrightnessAdjustment(), 'sharpen_intensity' => $this->calculateSharpenIntensity(), 'noise_reduction' => $this->calculateNoiseReduction() ]; return $this; } private function applyAdaptiveEnhancements() { if (!$this->processedImage || !isset($this->processingParams)) { return $this; } $params = $this->processingParams; // Verifica e aplica ajustes com validação de parâmetros if (isset($params['brightness_adjustment']) || isset($params['contrast_adjustment'])) { $brightness = isset($params['brightness_adjustment']) ? (int)$params['brightness_adjustment'] : 0; $contrast = isset($params['contrast_adjustment']) ? (int)$params['contrast_adjustment'] : 0; $this->applyBrightnessContrastOptimized($brightness, $contrast); } if (isset($params['noise_reduction']) && $params['noise_reduction'] > 0) { $this->applyFastNoiseReduction((int)$params['noise_reduction']); } if (isset($params['sharpen_intensity']) && $params['sharpen_intensity'] > 0) { $this->applyFastSharpening((int)$params['sharpen_intensity']); } return $this; } private function calculateAverageBrightness($histogram, $totalPixels) { $sum = 0; for ($i = 0; $i < 256; $i++) { $sum += $i * $histogram[$i]; } return $sum / $totalPixels; } private function calculateImageContrast($histogram, $totalPixels) { $mean = $this->imageQualityMetrics['brightness']; $variance = 0; for ($i = 0; $i < 256; $i++) { $variance += pow($i - $mean, 2) * ($histogram[$i] / $totalPixels); } return sqrt($variance); } private function estimateImageNoise($samples = 50) { $width = imagesx($this->srcImage); $height = imagesy($this->srcImage); $noise = 0; // Reduz número de amostras e otimiza cálculo for ($i = 0; $i < $samples; $i++) { $x = rand(1, $width - 2); $y = rand(1, $height - 2); $noise += $this->fastLocalVariance($x, $y); } return $noise / $samples; } private function estimateBlurLevel() { if (!$this->processedImage) { return 0; } // Cria uma cópia temporária para aplicar o Laplaciano $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); $temp = imagecreatetruecolor($width, $height); if (!$temp) { return 0; } imagecopy($temp, $this->processedImage, 0, 0, 0, 0, $width, $height); $laplacian = [ [0, 1, 0], [1, -4, 1], [0, 1, 0] ]; $blurValue = $this->applyConvolutionSafe($temp, $laplacian); imagedestroy($temp); return $blurValue; } private function calculateContrastAdjustment() { $contrast = $this->imageQualityMetrics['contrast']; $optimal = 128; // Valor ideal de contraste if ($contrast < $optimal * 0.7) { return (int)round(min(50, (1 - $contrast/$optimal) * 100)); } elseif ($contrast > $optimal * 1.3) { return (int)round(max(-50, ($optimal/$contrast - 1) * 100)); } return 0; } private function calculateBrightnessAdjustment() { $brightness = $this->imageQualityMetrics['brightness']; $optimal = 128; // Valor ideal de brilho // Forçar conversão explícita para inteiro return (int)round(($optimal - $brightness) * 0.5); } private function calculateSharpenIntensity() { $blurLevel = $this->imageQualityMetrics['blur']; return (int)round(min(100, $blurLevel * 50)); } private function calculateNoiseReduction() { $noise = $this->imageQualityMetrics['noise']; return (int)round(min(5, $noise * 2)); } private function calculateLocalVariance($x, $y) { $values = []; $windowSize = 3; $halfWindow = floor($windowSize / 2); // Coleta valores na janela for ($i = -$halfWindow; $i <= $halfWindow; $i++) { for ($j = -$halfWindow; $j <= $halfWindow; $j++) { $px = $x + $i; $py = $y + $j; if ($px >= 0 && $px < imagesx($this->srcImage) && $py >= 0 && $py < imagesy($this->srcImage)) { $rgb = imagecolorat($this->srcImage, $px, $py); $colors = imagecolorsforindex($this->srcImage, $rgb); $values[] = ($colors['red'] + $colors['green'] + $colors['blue']) / 3; } } } // Calcula variância local if (count($values) > 0) { $mean = array_sum($values) / count($values); $variance = array_sum(array_map(function($v) use ($mean) { return pow($v - $mean, 2); }, $values)) / count($values); return $variance; } return 0; } private function calculateVariance($image) { $width = imagesx($image); $height = imagesy($image); $values = []; // Amostra de pixels para cálculo $sampleSize = min(1000, $width * $height); for ($i = 0; $i < $sampleSize; $i++) { $x = rand(0, $width - 1); $y = rand(0, $height - 1); $rgb = imagecolorat($image, $x, $y); $colors = imagecolorsforindex($image, $rgb); $values[] = ($colors['red'] + $colors['green'] + $colors['blue']) / 3; } if (count($values) > 0) { $mean = array_sum($values) / count($values); return array_sum(array_map(function($v) use ($mean) { return pow($v - $mean, 2); }, $values)) / count($values); } return 0; } private function applyAdaptiveNoiseReduction($intensity) { $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); $temp = imagecreatetruecolor($width, $height); imagecopy($temp, $this->processedImage, 0, 0, 0, 0, $width, $height); // Garantir que intensity seja inteiro $intensity = (int)$intensity; $windowSize = 2 * $intensity + 1; $halfWindow = (int)floor($windowSize / 2); for ($x = $halfWindow; $x < $width - $halfWindow; $x++) { for ($y = $halfWindow; $y < $height - $halfWindow; $y++) { $values = []; for ($i = -$halfWindow; $i <= $halfWindow; $i++) { for ($j = -$halfWindow; $j <= $halfWindow; $j++) { $rgb = imagecolorat($temp, $x + $i, $y + $j); $colors = imagecolorsforindex($temp, $rgb); $values[] = $colors['red']; } } sort($values); // Garantir índice inteiro para o valor mediano $medianIndex = (int)floor(count($values) / 2); $median = $values[$medianIndex]; $color = imagecolorallocate($this->processedImage, $median, $median, $median); imagesetpixel($this->processedImage, $x, $y, $color); } } imagedestroy($temp); return $this; } private function applyAdaptiveSharpening($intensity) { $matrix = [ [-1, -1, -1], [-1, 8 + $intensity, -1], [-1, -1, -1] ]; $this->applyConvolution($matrix); return $this; } private function applyConvolutionSafe($image, $matrix) { if (!$image) { return 0; } $width = imagesx($image); $height = imagesy($image); $temp = imagecreatetruecolor($width, $height); if (!$temp) { return 0; } imagecopy($temp, $image, 0, 0, 0, 0, $width, $height); $variance = 0; $matrixSize = count($matrix); $halfSize = floor($matrixSize / 2); // Calcula apenas uma amostra para performance $sampleSize = min(1000, $width * $height); $values = []; for ($i = 0; $i < $sampleSize; $i++) { $x = rand($halfSize, $width - $halfSize - 1); $y = rand($halfSize, $height - $halfSize - 1); $sum = 0; for ($mx = -$halfSize; $mx <= $halfSize; $mx++) { for ($my = -$halfSize; $my <= $halfSize; $my++) { $rgb = imagecolorat($temp, $x + $mx, $y + $my); $colors = imagecolorsforindex($temp, $rgb); $gray = ($colors['red'] + $colors['green'] + $colors['blue']) / 3; $sum += $gray * $matrix[$mx + $halfSize][$my + $halfSize]; } } $values[] = abs($sum); } imagedestroy($temp); if (count($values) > 0) { return array_sum($values) / count($values); } return 0; } // Método auxiliar para verificar recursos de imagem private function isValidImage($image) { return $image && is_resource($image) || (is_object($image) && $image instanceof GdImage); } private function ensureProcessedImage() { if (!$this->processedImage) { $this->processedImage = $this->srcImage; } return $this->isValidImage($this->processedImage); } // Adicionar método para garantir valores inteiros private function ensureInteger($value) { return (int)round($value); } private function fastLocalVariance($x, $y) { $sum = 0; $sumSq = 0; $count = 0; // Analisa apenas 4 pixels vizinhos em vez de 9 $positions = [ [$x, $y], [$x-1, $y], [$x+1, $y], [$x, $y-1], [$x, $y+1] ]; foreach ($positions as [$px, $py]) { $rgb = imagecolorat($this->srcImage, $px, $py); $colors = imagecolorsforindex($this->srcImage, $rgb); $value = ($colors['red'] + $colors['green'] + $colors['blue']) / 3; $sum += $value; $sumSq += $value * $value; $count++; } $mean = $sum / $count; return ($sumSq / $count) - ($mean * $mean); } private function applyBrightnessContrastOptimized($brightness, $contrast) { $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); // Pré-calcula tabela de lookup para otimizar $lookup = array_fill(0, 256, 0); for ($i = 0; $i < 256; $i++) { $value = $i; if ($brightness != 0) { $value += $brightness; } if ($contrast != 0) { $value = (($value - 128) * (1 + $contrast/100)) + 128; } $lookup[$i] = min(255, max(0, (int)$value)); } // Aplica lookup table em uma única passagem for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $rgb = imagecolorat($this->processedImage, $x, $y); $colors = imagecolorsforindex($this->processedImage, $rgb); $newColor = imagecolorallocate( $this->processedImage, $lookup[$colors['red']], $lookup[$colors['green']], $lookup[$colors['blue']] ); imagesetpixel($this->processedImage, $x, $y, $newColor); } } } private function applyFastNoiseReduction($intensity) { if (!$this->processedImage) { return $this; } $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); // Otimização: processa apenas uma parte dos pixels $step = max(1, (int)($intensity / 2)); for ($x = 0; $x < $width - 2; $x += $step) { for ($y = 0; $y < $height - 2; $y += $step) { $values = []; // Coleta 5 pixels em vez de 9 para maior velocidade $pixels = [ [$x, $y], [$x + 1, $y], [$x, $y + 1], [$x + 1, $y + 1], [$x + 2, $y + 2] ]; foreach ($pixels as [$px, $py]) { if ($px < $width && $py < $height) { $rgb = imagecolorat($this->processedImage, $px, $py); $colors = imagecolorsforindex($this->processedImage, $rgb); $values[] = $colors['red']; } } if (!empty($values)) { sort($values); $median = $values[(int)(count($values) / 2)]; $color = imagecolorallocate($this->processedImage, $median, $median, $median); if ($color !== false) { imagesetpixel($this->processedImage, $x, $y, $color); } } } } return $this; } private function applyFastSharpening($intensity) { if (!$this->processedImage) { return $this; } // Matriz de sharpening otimizada $matrix = [ [-0.5, -0.5, -0.5], [-0.5, 6 + ($intensity * 0.1), -0.5], [-0.5, -0.5, -0.5] ]; $width = imagesx($this->processedImage); $height = imagesy($this->processedImage); $temp = imagecreatetruecolor($width, $height); if (!$temp) { return $this; } imagecopy($temp, $this->processedImage, 0, 0, 0, 0, $width, $height); // Processamento otimizado com passo variável $step = max(1, min(3, (int)($width * $height / 1000000))); for ($x = 1; $x < $width - 1; $x += $step) { for ($y = 1; $y < $height - 1; $y += $step) { $r = $g = $b = 0; for ($i = -1; $i <= 1; $i++) { for ($j = -1; $j <= 1; $j++) { $rgb = imagecolorat($temp, $x + $i, $y + $j); $colors = imagecolorsforindex($temp, $rgb); $weight = $matrix[$i + 1][$j + 1]; $r += $colors['red'] * $weight; $g += $colors['green'] * $weight; $b += $colors['blue'] * $weight; } } $r = min(255, max(0, (int)$r)); $g = min(255, max(0, (int)$g)); $b = min(255, max(0, (int)$b)); $color = imagecolorallocate($this->processedImage, $r, $g, $b); if ($color !== false) { imagesetpixel($this->processedImage, $x, $y, $color); } } } imagedestroy($temp); return $this; } }