# PRD – Java‑native Bildkonvertierung (Spring Boot, OSS) **Ziel:** Plattformneutrale (Linux/Windows, Docker/K8s) Java‑Komponente zur Konvertierung beliebiger Eingangsbildformate in ein konfigurierbares Zielformat (Standard: PNG), ohne native Abhängigkeiten oder OS‑Pakete. --- ## 1. Hintergrund & Motivation - Ingest erhält Bilder aus heterogenen Quellen (JPEG, TIFF, PSD, PNM/TGA/IFF/ICNS, WebP, ggf. JP2 …). - Kundenanforderung: *Keine* nativen Tools/OS‑Pakete; alles soll innerhalb des Java‑Artefakts laufen. - ImageIO + Plugins (TwelveMonkeys, JP2/WebP) decken breite Formate ab. HEIC ist **out of scope** in dieser OSS‑Variante (kann später mit kommerziellen Libs ergänzt werden). --- ## 2. Scope **In Scope** - Java/Spring Boot‑Bibliothek + optionaler REST‑Controller. - Übergabe **byte[]** (Quellbild), Liste **erlaubter Zielformate** und **präferiertes Zielformat**. - Wenn Quellformat ∈ erlaubte Zielformate → **unverändert** zurückgeben. - Sonst Konvertierung → **präferiertes Zielformat**. - Optionaler Qualitätsparameter (0..1) für verlustbehaftete Formate (JPEG/WebP). - Mehrseitige Formate (TIFF/ICO/animierte GIFs): mindestens **Frame 0**; API erweitertbar für `frameIndex`. - SPI‑basierte Formatdetektion (ImageIO) ohne MIME‑Erkennung; Tika optional (Adapter, nicht standardmäßig aktiv). **Out of Scope (Phase 1)** - HEIC/HEIF, AVIF, JPEG‑XL (kann in Phase 2 über JDeli/Aspose Imaging ergänzt werden). - Komplettes Metadaten‑/ICC‑Profile‑Roundtripping (nur baseline: übernehmen, sofern Writer/Format es unterstützt). - Animations-Erhalt (z. B. GIF→GIF animiert). Standard: erstes Frame. --- ## 3. Ziele & Akzeptanzkriterien **Funktional** 1. `negotiateAndConvert(byte[], allowed, preferred, quality)` liefert: - **Unverändert**es Byte‑Array falls Quellformat∈allowed erkannt wird. - Andernfalls konvertiert in `preferred` unter Beachtung des Quality‑Hints (falls anwendbar). 2. Erkennung des Quellformats ohne MIME‑Type ausschließlich über ImageIO/SPI. 3. Unterstützte Ziel‑Formate mindestens: **PNG, JPEG, GIF, BMP, TIFF**, optional **WEBP**, optional **JP2**. **Nicht‑funktional** - **Portabel**: läuft identisch auf **Linux** und **Windows** Containern. - **Zero‑native**: keine OS‑Pakete, nur Java‑Abhängigkeiten. - **Lieferform**: Spring‑Boot‑Fat‑JAR oder Layered‑JAR; Container‑Image ohne zusätzliche Systemlibs. - **Performance**: Einzelbild‑Konvertierung < 150 ms (warm) für 2–8 MP JPEG→PNG auf x86\_64 Server‑HW (Anhaltswert). - **Speicher**: hartes Limit pro Bild verifizierbar (z. B. 128 MB Heap‑Budget pro Request) via Größe‑/Dimension‑Checks. --- ## 4. Architektur & Design ### 4.1 Komponenten - ``** (enum)**: hält `extension`, `mimeType`, `imageIoFormatName` (z. B. PNG→"png"). - ``: generische Konvertierung `InputStream → byte[]` in gewünschtes Zielformat; optional `frameIndex` & `quality`. - ``: nimmt `byte[]` + `allowedTargets` + `preferredTarget`; nutzt ImageIO‑SPI, entscheidet Pass‑Through vs. Konvertierung. - **(Optional) REST‑Controller**: Multipart‑Endpoint `/convert` und `/negotiate`. ### 4.2 Datenfluss 1. `negotiateAndConvert` erstellt `ImageInputStream` auf `byte[]`. 2. `ImageIO.getImageReaders(iis)` → erster `ImageReader` + `ImageReaderSpi` → Namen/Suffixe/MIMEs sammeln; Vergleich mit `allowedTargets`. 3. Wenn Match → Rückgabe `byte[]` unverändert. Sonst → `ImageConversionService.convert(..., preferred)`. ### 4.3 SPI & Plugins - **ImageIO‑SPI** lädt Reader/Writer per `META-INF/services` der Abhängigkeiten. - **TwelveMonkeys** erweitert u. a. TIFF/PSD/PNM/TGA/IFF/ICNS u. v. m. - **JP2** über `jai-imageio-jpeg2000` (optional; Lizenzhinweis JJ2000 beachten). - **WebP** über `org.sejda.imageio:webp-imageio` (optional). ### 4.4 Fehlermanagement - **Technische Fehler**: `IOException` (I/O), `IndexOutOfBoundsException` (Frame), `IllegalArgumentException` (Parametrisierung) → REST: 4xx/5xx mapping. - **Unsupported**: kein Reader/Writer gefunden → 415 (Unsupported Media Type) im REST‑Layer. ### 4.5 Sicherheit/Robustheit - **Input‑Limits**: Max‑Bytes pro Upload (z. B. 32 MB), Max‑Dimension (z. B. 12k×12k), Max‑Frames. - **ZIP/Decompress‑Bomb Schutz**: Ablehnung extrem großer Canvas/Pixelanzahlen; frühzeitiges Abbrechen. - **Timeouts**: Konvertierung per `@Async`/Executor mit Timeout (Controller‑Schicht) optional. - **Dependency Hygiene**: regelm. Updates (TwelveMonkeys/WebP/JP2), CVE‑Monitoring. ### 4.6 Observability & Logging - **Micrometer**: Timer `image.convert`, Tags: `src.detected`, `dst.format`, `converted` (true/false), `exception`. - **Structured Logging**: auf INFO: Zusammenfassung (ohne PII/Bilddaten), DEBUG: techn. Details. --- ## 5. Öffentliche API (Java) ### 5.1 Enum `OutputImageFormat` - Felder: `defaultExtension:String`, `mimeType:String`, `imageIoFormatName:String`. - Werte (Phase 1): `PNG, JPEG, GIF, BMP, TIFF` (+ optional `WEBP`, `JP2`). - Methoden: `extension()`, `mimeType()`, `imageIoFormatName()`, `fromExtension(String)`. ### 5.2 `ImageConversionService` ```java byte[] convert(InputStream in, OutputImageFormat target, Float quality) throws IOException; byte[] convert(InputStream in, OutputImageFormat target, int frameIndex, Float quality) throws IOException; ``` ### 5.3 `ImageFormatNegotiatorService` ```java NegotiationResult negotiateAndConvert(byte[] sourceBytes, List allowedTargets, OutputImageFormat preferredTarget, Float quality) throws IOException; final class NegotiationResult { byte[] data(); OutputImageFormat finalFormat(); boolean converted(); String detectedSourceFormat(); // optional } ``` ### 5.4 REST (optional) - `POST /convert` (multipart: `file`, `target`, optional `quality`) - `POST /negotiate` (multipart: `file`, `allowed`=CSV, `preferred`, optional `quality`) - Responses: `200 OK` mit `Content-Type` gemäß Zielformat; `415` bei nicht unterstützten Quellen/Zielen. ### 5.5 CSV-Parser `ImageFormatParsers` ```java List allowed = ImageFormatParsers.parseCsv(params.getAllowed()); OutputImageFormat preferred = ImageFormatParsers.lookup(params.getPreferred()) .orElseThrow(() -> new IllegalArgumentException("Unsupported preferred format")); if (!allowed.contains(preferred)) { throw new IllegalArgumentException("Preferred must be included in allowed"); } List defaults = ImageFormatParsers.parseCsvLenient(env.getProperty("images.allowed-default")); ``` --- ## 6. Build & Packaging - **Spring Boot**: `spring-boot-maven-plugin` → `repackage` Fat‑JAR (Nested JARs in `BOOT-INF/lib`). - **Ohne Shade** empfohlen: SPI‑Files bleiben automatisch intakt. - **Falls Shade unbedingt nötig**: `ServicesResourceTransformer` aktivieren (merge `META-INF/services`). - **Container**: Base `eclipse-temurin:21-jre` (Linux) bzw. Windows‑Servercore‑Variante; nur JAR kopieren. --- ## 7. Abhängigkeiten (Beispiel‑POM, Versionen als Property) - `com.twelvemonkeys.imageio:imageio-core`, `imageio-tiff`, `imageio-psd`, optional `imageio-pnm`, `imageio-tga`, `imageio-iff`, `imageio-icns` … - **Optional JP2**: `com.github.jai-imageio:jai-imageio-core`, `jai-imageio-jpeg2000` (Lizenz prüfen) - **Optional WebP**: `org.sejda.imageio:webp-imageio` --- ## 8. Konfiguration `application.yml` (Beispiele): ```yaml images: allowed-default: [PNG, JPEG] preferred-default: PNG max-bytes: 33554432 # 32 MB max-width: 12000 max-height: 12000 max-frames: 1 ``` --- ## 9. Teststrategie **Unit‑Tests** - Parametrisierte Tests über alle `OutputImageFormat` Ziele (PNG/JPEG/GIF/BMP/TIFF/[WEBP]/[JP2]). - Erkennungstests: Reader/SPI‑Detection für diverse Quellen (jpg/jpeg, tif/tiff, pnm/ppm/pgm, tga, icns, iff/ilbm, psd, png, gif, bmp, webp, jp2) – kleine Testbilder ins Repo (Lizenzfrei!). - Negotiation‑Pfad: (a) pass‑through, (b) convert, (c) unsupported. - Quality‑Pfad: JPEG/WebP Qualität 0.6/0.9. - Fehlerpfade: korruptes Bild, leeres Byte‑Array, zu groß, falscher FrameIndex. **Integration‑Tests** - REST‑Endpoints mit Multipart; Content‑Type und Dateiname (Endung vom Enum) verifizieren. **Performance‑Tests** - Warmlauf + Messung (JMH oder simple Stopwatch) für typische Größen (2MP, 8MP). --- ## 10. Risiken & Gegenmaßnahmen - **Plugin‑Abdeckung**: WebP/JP2 benötigen Zusatzmodule → klare Optionalität & Feature‑Flags. - **Große Bilder/DoS**: harte Limits, frühzeitiger Abbruch. - **Metadatenverlust**: dokumentieren; falls relevant, Folge‑Ticket für EXIF/ICC‑Handling. - **Lizenzthemen JP2**: JJ2000‑Lizenz prüfen, Compliance aufnehmen. --- ## 11. Erweiterungen (Phase 2) - **HEIC/HEIF**: via JDeli oder Aspose.Imaging (kommerziell), gleiche API beibehalten. - **Mehrseiten‑Handling**: Seitenliste/Range, Animationserhalt für GIF/WebP. - **Farbraum**: explizites ICC‑Profile‑Handling & Konvertierung. --- ## 12. Umsetzungsfahrplan (für Codex) 1. **Enum** `OutputImageFormat` anlegen (PNG/JPEG/GIF/BMP/TIFF + optional WEBP/JP2). 2. **Service** `ImageConversionService` implementieren (Reader→BufferedImage→Writer; Quality nur wenn `canWriteCompressed`). 3. **Negotiator** `ImageFormatNegotiatorService` implementieren (SPI‑Detection via `ImageReaderSpi`: Namen/Suffixe/MIMEs; Pass‑Through vs. Convert). 4. **Controller** (optional) + DTOs + Validation. 5. **POM**: TwelveMonkeys‑Module + optional WebP/JP2. 6. **Tests**: Unit + Integration + Beispielbilder. 7. **Observability**: Micrometer‑Timer & Logs. 8. **Dockerfile** (Linux), optional Windows‑Image; K8s‑Manifest‑Snippets (Resources/Limits/Probes). --- ## 13. Akzeptanz‑Checkliste (DoD) - --- ## 14. Anhang – Code‑Skeletons (Kurz) **Enum** (Auszug) ```java enum OutputImageFormat { PNG("png", "image/png", "png"), JPEG("jpg", "image/jpeg", "jpeg"), GIF("gif", "image/gif", "gif"), BMP("bmp", "image/bmp", "bmp"), TIFF("tiff", "image/tiff", "tiff"), WEBP("webp", "image/webp", "webp"), // optional JP2("jp2", "image/jp2", "jpeg2000"); // optional // getter + fromExtension } ``` **ImageConversionService** (Signaturen siehe §5.2) – BufferedImage lesen/schreiben; `ImageWriteParam` nur bei `canWriteCompressed()` + `setCompressionQuality(quality)`. **Negotiator** – `ImageReaderSpi` inspizieren (`getFormatNames()`, `getFileSuffixes()`, `getMIMETypes()`), tolerante Matches für jpg/jpeg & tif/tiff. **POM Snippets** ```xml com.twelvemonkeys.imageio imageio-core ${twelvemonkeys.version} com.twelvemonkeys.imageio imageio-tiff ${twelvemonkeys.version} com.twelvemonkeys.imageio imageio-psd ${twelvemonkeys.version} com.github.jai-imageio jai-imageio-core ${jai.version} com.github.jai-imageio jai-imageio-jpeg2000 ${jai.version} org.sejda.imageio webp-imageio ${webp.version} ``` **Build** - `spring-boot-maven-plugin` → `repackage` Fat‑JAR. - (Nur falls Shade) `ServicesResourceTransformer` zum Mergen der `META-INF/services`. **Docker (Linux, Beispiel)** ```dockerfile FROM eclipse-temurin:21-jre WORKDIR /opt/app COPY target/app.jar app.jar ENTRYPOINT ["java","-jar","/opt/app/app.jar"] ``` — Ende —