- B-01/B-02: Startseiten-Feed, Card-Helfer und Nachweis-PDF nur mit isPubliclyDeliverable() - B-03: Livewire-Temp-Upload 16 MiB - N09: Snapshot je Bytes- und Metadaten-Hash inkl. OG-Variante, put() geprüft, alle Fassungen ins Archiv - O03: published_ever_at in der Freigabetransaktion (markPublishedIfNowPublic) — Kundenbestätigung, Vorab-Sichtung, Korrektur - B-04/B-14: Bestätigung an Bild-ID und SHA gebunden, unter Sperre - B-05/B-06: Entfernen unter Sperre mit frischem Stand und Archivauftrag; Korrektur archiviert über den Auftrag, Nacharbeiten abgesichert - B-07: precheck_withheld_at getrennt von withheld_at (Migration + Übernahme) - B-08, B-09, B-11, B-12: published_ever_at nur bei Status published, Admin-Eigentümer mit Kundenregel, Sweep verwaister Originale, Verarbeitungsfehler als Formularmeldung - K-02-Teil: nach Kundenbestätigung erneut routen - OPT-3 bis OPT-5: pingImage für JPEG, Hash durchgereicht, erstes Frame Admin-PM-Detailseite enthält zusätzlich K-06 (Autorisierung) und A-08 (Nachweis per POST), weil die Datei ungeteilt committet ist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.1 KiB
PHP
67 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Image\Precheck;
|
|
|
|
use Imagick;
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* Datenminimierung vor dem Modellaufruf (DU 12.3, 12.6): lange Kante auf
|
|
* 1024 px, alle Metadaten gestrippt, JPEG ohne Alphakanal. Es verlässt nie
|
|
* das Original die Plattform.
|
|
*/
|
|
class PrecheckImagePreparer
|
|
{
|
|
/**
|
|
* @return array{bytes: string, width: int, height: int}
|
|
*/
|
|
public function prepare(string $absolutePath): array
|
|
{
|
|
if (! class_exists(Imagick::class)) {
|
|
throw new RuntimeException('Imagick ist nicht verfügbar.');
|
|
}
|
|
|
|
$maxEdge = max(256, (int) config('images.precheck.max_edge', 1024));
|
|
$quality = (int) config('images.precheck.jpeg_quality', 82);
|
|
|
|
$source = new Imagick;
|
|
$imagick = $source;
|
|
|
|
try {
|
|
// Nur das erste Bild lesen (OPT-5): Animationen und Mehrseiter
|
|
// werden nicht vollständig dekodiert.
|
|
$source->readImage($absolutePath.'[0]');
|
|
$source->setIteratorIndex(0);
|
|
|
|
$source->autoOrient();
|
|
|
|
$source->setImageBackgroundColor('white');
|
|
$source->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
|
|
$imagick = $source->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
|
|
$source->clear();
|
|
$imagick->stripImage();
|
|
|
|
$width = $imagick->getImageWidth();
|
|
$height = $imagick->getImageHeight();
|
|
|
|
if (max($width, $height) > $maxEdge) {
|
|
$imagick->resizeImage($maxEdge, $maxEdge, Imagick::FILTER_LANCZOS, 1, true);
|
|
$width = $imagick->getImageWidth();
|
|
$height = $imagick->getImageHeight();
|
|
}
|
|
|
|
$imagick->setImageFormat('jpeg');
|
|
$imagick->setImageCompressionQuality($quality);
|
|
$bytes = $imagick->getImageBlob();
|
|
} finally {
|
|
$imagick->clear();
|
|
$source->clear();
|
|
}
|
|
|
|
if ($bytes === '') {
|
|
throw new RuntimeException('Das Bild konnte nicht für die Sichtung vorbereitet werden.');
|
|
}
|
|
|
|
return ['bytes' => $bytes, 'width' => $width, 'height' => $height];
|
|
}
|
|
}
|