Files
michaelschiemer/src/Domain/Media/ImagickImageProcessor.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

246 lines
7.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Domain\Media;
use Imagick;
use ImagickException;
/**
* ImageMagick-basierter Bildprozessor für optimale Bildqualität
*/
final readonly class ImagickImageProcessor implements ImageProcessorInterface
{
/**
* @inheritDoc
*/
public function createAllVariants(Image $image): array
{
$variants = [];
foreach (ImageVariantConfig::getAllVariants() as $variantConfig) {
try {
$variant = $this->createVariant($image, $variantConfig);
$variants[] = $variant;
} catch (\Exception $e) {
error_log("Failed to create variant {$variantConfig['type']->value}_{$variantConfig['size']->value}_{$variantConfig['format']->value}: " . $e->getMessage());
}
}
return $variants;
}
/**
* @inheritDoc
*/
public function createVariantsForType(Image $image, ImageVariantType $type): array
{
$variants = [];
foreach (ImageVariantConfig::getVariantsForType($type) as $variantConfig) {
try {
$variant = $this->createVariant($image, $variantConfig);
$variants[] = $variant;
} catch (\Exception $e) {
error_log("Failed to create variant {$variantConfig['type']->value}_{$variantConfig['size']->value}_{$variantConfig['format']->value}: " . $e->getMessage());
}
}
return $variants;
}
/**
* Erstellt eine einzelne Bildvariante
*
* @param Image $image
* @param array $config
* @return ImageVariant
*/
private function createVariant(Image $image, array $config): ImageVariant
{
$sourcePath = $image->path->join($image->filename)->toString();
/** @var ImageVariantType $type */
/** @var ImageSize $size */
/** @var ImageFormat $format */
$type = $config['type'];
$size = $config['size'];
$format = $config['format'];
$width = $config['width'];
$filename = $this->generateVariantFilename(
$image->filename,
$type->value,
$size->value,
$format->value
);
$destination = $image->path->join($filename)->toString();
$actualDimensions = $this->processImage($sourcePath, $destination, $width, $format);
$fileSize = filesize($destination);
return new ImageVariant(
imageId: (string) $image->ulid,
variantType: $type->value,
size: $size->value,
format: $format->value,
mimeType: $format->getMimeType(),
fileSize: $fileSize,
width: $actualDimensions['width'],
height: $actualDimensions['height'],
filename: $filename,
path: $image->path->toString(),
);
}
/**
* Generiert einen Dateinamen für eine Bildvariante
*/
private function generateVariantFilename(string $originalFilename, string $type, string $size, string $format): string
{
$pathInfo = pathinfo($originalFilename);
return $pathInfo['filename'] . "_{$type}_{$size}.{$format}";
}
/**
* Verarbeitet das Bild mit ImageMagick
*
* @param string $sourcePath
* @param string $destination
* @param int $maxWidth
* @param ImageFormat $format
* @return array{width: int, height: int}
*/
private function processImage(string $sourcePath, string $destination, int $maxWidth, ImageFormat $format): array
{
try {
$imagick = new Imagick($sourcePath);
// Bildorientierung korrigieren (EXIF)
$imagick->autoOrientImage();
// Farbprofil entfernen für kleinere Dateien
$imagick->stripImage();
// Bessere Resampling-Methode
$imagick->setImageResolution(72, 72);
// Original-Dimensionen speichern
$originalWidth = $imagick->getImageWidth();
$originalHeight = $imagick->getImageHeight();
// Skalierung nur wenn nötig
if ($originalWidth > $maxWidth) {
$scale = $maxWidth / $originalWidth;
$newWidth = $maxWidth;
$newHeight = (int)round($originalHeight * $scale);
// Lanczos-Filter für beste Qualität
$imagick->resizeImage($newWidth, $newHeight, Imagick::FILTER_LANCZOS, 1);
// Schärfen bei Verkleinerung
$this->applySharpeningAfterResize($imagick, $originalWidth, $newWidth);
} else {
$newWidth = $originalWidth;
$newHeight = $originalHeight;
}
// Format-spezifische Optimierungen
$this->optimizeForFormat($imagick, $format);
// Speichern
$imagick->writeImage($destination);
$imagick->destroy();
return ['width' => $newWidth, 'height' => $newHeight];
} catch (ImagickException $e) {
throw new \RuntimeException('ImageMagick error: ' . $e->getMessage(), 0, $e);
}
}
/**
* Wendet formatspezifische Optimierungen an
*/
private function optimizeForFormat(Imagick $imagick, ImageFormat $format): void
{
match ($format) {
ImageFormat::JPEG => $this->optimizeJpeg($imagick),
ImageFormat::WEBP => $this->optimizeWebP($imagick),
ImageFormat::AVIF => $this->optimizeAvif($imagick),
};
}
/**
* Optimiert JPEG-Einstellungen
*/
private function optimizeJpeg(Imagick $imagick): void
{
$imagick->setImageFormat('jpeg');
$imagick->setImageCompressionQuality(ImageFormat::JPEG->getQuality());
$imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
// Progressive JPEG für bessere Ladezeiten
$imagick->setInterlaceScheme(Imagick::INTERLACE_PLANE);
// Chroma-Subsampling für kleinere Dateien
$imagick->setSamplingFactors(['2x2', '1x1', '1x1']);
}
/**
* Optimiert WebP-Einstellungen
*/
private function optimizeWebP(Imagick $imagick): void
{
$imagick->setImageFormat('webp');
$imagick->setImageCompressionQuality(ImageFormat::WEBP->getQuality());
// WebP-spezifische Optionen
$imagick->setOption('webp:lossless', 'false');
$imagick->setOption('webp:alpha-quality', '85');
$imagick->setOption('webp:method', '4'); // Balancierte Kompression
}
/**
* Optimiert AVIF-Einstellungen
*/
private function optimizeAvif(Imagick $imagick): void
{
// Wenn die ImageMagick-Version AVIF direkt unterstützt
if (in_array('AVIF', Imagick::queryFormats())) {
$imagick->setImageFormat('avif');
$imagick->setImageCompressionQuality(ImageFormat::AVIF->getQuality());
// AVIF-spezifische Optionen, falls unterstützt
$imagick->setOption('avif:speed', '6'); // Balancierte Kompression
} else {
// Fallback: Speichern als WebP wenn AVIF nicht unterstützt wird
$this->optimizeWebP($imagick);
}
}
/**
* Wendet intelligente Schärfung an, um Details zu verbessern
*/
private function applySharpeningAfterResize(Imagick $imagick, int $originalWidth, int $newWidth): void
{
$scaleFactor = $newWidth / $originalWidth;
// Nur bei signifikanter Verkleinerung schärfen
if ($scaleFactor < 0.8) {
// Stärke der Schärfung an die Verkleinerung anpassen
$radius = 0.5;
$sigma = 0.5;
$amount = 0.8 + ((1 - $scaleFactor) * 0.3); // Mehr Schärfung bei größerer Verkleinerung
$threshold = 0.05;
$imagick->unsharpMaskImage($radius, $sigma, $amount, $threshold);
}
}
}