testCropJpeg(); $this->testCropPng(); $this->testNoCropWhenBorderTooSmall(); $this->testAllWhiteReturnsNoChange(); $this->testUnsupportedMimeReturnsFalse(); } // ------------------------------------------------------------------------- private function testCropJpeg(): void { // 300×200 weißes Bild, rotes Rechteck 50×50 px bei (100, 75) $img = imagecreatetruecolor(300, 200); $white = imagecolorallocate($img, 255, 255, 255); $red = imagecolorallocate($img, 200, 50, 50); imagefill($img, 0, 0, $white); imagefilledrectangle($img, 100, 75, 149, 124, $red); $path = $this->saveTmp($img, 'jpeg'); imagedestroy($img); $service = new \Steuerkram\Services\ImageCropService(); $result = $service->cropWhitespace($path); $this->assert($result === true, 'JPEG: cropWhitespace() gibt true zurück'); [$newW, $newH] = getimagesize($path); // Erwartete Maße: Rechteck (100,75)–(149,124) + PADDING 25 ringsum → 100×100. // JPEG-Verlust-Komprimierung kann Farbe ±2px in Nachbar-DCT-Blöcke bluten lassen, // daher Toleranz von 2px je Seite. $this->assert($newW <= 104 && $newW >= 96, "JPEG: Breite nach Crop im Bereich 96–104 px (erhalten: {$newW})"); $this->assert($newH <= 104 && $newH >= 96, "JPEG: Höhe nach Crop im Bereich 96–104 px (erhalten: {$newH})"); } private function testCropPng(): void { // 400×300 weißes PNG, blaues Rechteck 80×60 px bei (160, 120) $img = imagecreatetruecolor(400, 300); $white = imagecolorallocate($img, 255, 255, 255); $blue = imagecolorallocate($img, 50, 100, 200); imagefill($img, 0, 0, $white); imagefilledrectangle($img, 160, 120, 239, 179, $blue); $path = $this->saveTmp($img, 'png'); imagedestroy($img); $service = new \Steuerkram\Services\ImageCropService(); $result = $service->cropWhitespace($path); $this->assert($result === true, 'PNG: cropWhitespace() gibt true zurück'); [$newW, $newH] = getimagesize($path); // cropX=135, cropY=95, cropW=130, cropH=110 $this->assertEquals(130, $newW, 'PNG: Breite nach Crop korrekt (80px + 2×25 Padding)'); $this->assertEquals(110, $newH, 'PNG: Höhe nach Crop korrekt (60px + 2×25 Padding)'); } private function testNoCropWhenBorderTooSmall(): void { // Inhalt füllt fast das gesamte Bild – weniger als 3 % Rand $img = imagecreatetruecolor(200, 200); $white = imagecolorallocate($img, 255, 255, 255); $red = imagecolorallocate($img, 200, 50, 50); imagefill($img, 0, 0, $white); // Inhalt lässt nur 1px Rand ringsum → Einsparung < 3 % imagefilledrectangle($img, 1, 1, 198, 198, $red); $path = $this->saveTmp($img, 'jpeg'); imagedestroy($img); $originalSize = filesize($path); $service = new \Steuerkram\Services\ImageCropService(); $result = $service->cropWhitespace($path); $this->assert($result === false, 'Kein Crop wenn Randersparnis < 3 %'); [$newW, $newH] = getimagesize($path); $this->assertEquals(200, $newW, 'Bildbreite bleibt unverändert bei minimalem Rand'); } private function testAllWhiteReturnsNoChange(): void { $img = imagecreatetruecolor(100, 100); $white = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $white); $path = $this->saveTmp($img, 'jpeg'); imagedestroy($img); $service = new \Steuerkram\Services\ImageCropService(); $result = $service->cropWhitespace($path); $this->assert($result === false, 'Komplett weißes Bild: kein Crop, gibt false zurück'); } private function testUnsupportedMimeReturnsFalse(): void { // Erstellt eine Dummy-Textdatei – MIME-Typ wird nicht als JPEG/PNG erkannt $path = $this->tempPath('.txt'); file_put_contents($path, 'kein Bild'); $service = new \Steuerkram\Services\ImageCropService(); $result = $service->cropWhitespace($path); $this->assert($result === false, 'Nicht-Bild-Datei: cropWhitespace() gibt false zurück'); } // ------------------------------------------------------------------------- private function saveTmp(\GdImage $img, string $type): string { $path = $this->tempPath('.' . $type); if ($type === 'jpeg') { imagejpeg($img, $path, 92); } else { imagepng($img, $path); } return $path; } }