image-converter/prd/Bildkonvertierung Java.md
2025-10-26 09:44:48 +01:00

311 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PRD Javanative Bildkonvertierung (Spring Boot, OSS)
**Ziel:** Plattformneutrale (Linux/Windows, Docker/K8s) JavaKomponente zur Konvertierung beliebiger Eingangsbildformate in ein konfigurierbares Zielformat (Standard: PNG), ohne native Abhängigkeiten oder OSPakete.
---
## 1. Hintergrund & Motivation
- Ingest erhält Bilder aus heterogenen Quellen (JPEG, TIFF, PSD, PNM/TGA/IFF/ICNS, WebP, ggf. JP2 …).
- Kundenanforderung: *Keine* nativen Tools/OSPakete; alles soll innerhalb des JavaArtefakts laufen.
- ImageIO + Plugins (TwelveMonkeys, JP2/WebP) decken breite Formate ab. HEIC ist **out of scope** in dieser OSSVariante (kann später mit kommerziellen Libs ergänzt werden).
---
## 2. Scope
**In Scope**
- Java/Spring BootBibliothek + optionaler RESTController.
- Ü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`.
- SPIbasierte Formatdetektion (ImageIO) ohne MIMEErkennung; Tika optional (Adapter, nicht standardmäßig aktiv).
**Out of Scope (Phase 1)**
- HEIC/HEIF, AVIF, JPEGXL (kann in Phase 2 über JDeli/Aspose Imaging ergänzt werden).
- Komplettes Metadaten/ICCProfileRoundtripping (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 ByteArray falls Quellformat∈allowed erkannt wird.
- Andernfalls konvertiert in `preferred` unter Beachtung des QualityHints (falls anwendbar).
2. Erkennung des Quellformats ohne MIMEType ausschließlich über ImageIO/SPI.
3. Unterstützte ZielFormate mindestens: **PNG, JPEG, GIF, BMP, TIFF**, optional **WEBP**, optional **JP2**.
**Nichtfunktional**
- **Portabel**: läuft identisch auf **Linux** und **Windows** Containern.
- **Zeronative**: keine OSPakete, nur JavaAbhängigkeiten.
- **Lieferform**: SpringBootFatJAR oder LayeredJAR; ContainerImage ohne zusätzliche Systemlibs.
- **Performance**: EinzelbildKonvertierung < 150ms (warm) für 28MP JPEGPNG auf x86\_64 ServerHW (Anhaltswert).
- **Speicher**: hartes Limit pro Bild verifizierbar (z.B. 128MB HeapBudget pro Request) via Größe/DimensionChecks.
---
## 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 ImageIOSPI, entscheidet PassThrough vs. Konvertierung.
- **(Optional) RESTController**: MultipartEndpoint `/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
- **ImageIOSPI** 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 RESTLayer.
### 4.5 Sicherheit/Robustheit
- **InputLimits**: MaxBytes pro Upload (z.B. 32MB), MaxDimension (z.B. 12k×12k), MaxFrames.
- **ZIP/DecompressBomb Schutz**: Ablehnung extrem großer Canvas/Pixelanzahlen; frühzeitiges Abbrechen.
- **Timeouts**: Konvertierung per `@Async`/Executor mit Timeout (ControllerSchicht) optional.
- **Dependency Hygiene**: regelm. Updates (TwelveMonkeys/WebP/JP2), CVEMonitoring.
### 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<OutputImageFormat> 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<OutputImageFormat> 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<OutputImageFormat> defaults = ImageFormatParsers.parseCsvLenient(env.getProperty("images.allowed-default"));
```
---
## 6. Build & Packaging
- **Spring Boot**: `spring-boot-maven-plugin` `repackage` FatJAR (Nested JARs in `BOOT-INF/lib`).
- **Ohne Shade** empfohlen: SPIFiles bleiben automatisch intakt.
- **Falls Shade unbedingt nötig**: `ServicesResourceTransformer` aktivieren (merge `META-INF/services`).
- **Container**: Base `eclipse-temurin:21-jre` (Linux) bzw. WindowsServercoreVariante; nur JAR kopieren.
---
## 7. Abhängigkeiten (BeispielPOM, 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
**UnitTests**
- Parametrisierte Tests über alle `OutputImageFormat` Ziele (PNG/JPEG/GIF/BMP/TIFF/[WEBP]/[JP2]).
- Erkennungstests: Reader/SPIDetection 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!).
- NegotiationPfad: (a) passthrough, (b) convert, (c) unsupported.
- QualityPfad: JPEG/WebP Qualität 0.6/0.9.
- Fehlerpfade: korruptes Bild, leeres ByteArray, zu groß, falscher FrameIndex.
**IntegrationTests**
- RESTEndpoints mit Multipart; ContentType und Dateiname (Endung vom Enum) verifizieren.
**PerformanceTests**
- Warmlauf + Messung (JMH oder simple Stopwatch) für typische Größen (2MP, 8MP).
---
## 10. Risiken & Gegenmaßnahmen
- **PluginAbdeckung**: WebP/JP2 benötigen Zusatzmodule klare Optionalität & FeatureFlags.
- **Große Bilder/DoS**: harte Limits, frühzeitiger Abbruch.
- **Metadatenverlust**: dokumentieren; falls relevant, FolgeTicket für EXIF/ICCHandling.
- **Lizenzthemen JP2**: JJ2000Lizenz prüfen, Compliance aufnehmen.
---
## 11. Erweiterungen (Phase 2)
- **HEIC/HEIF**: via JDeli oder Aspose.Imaging (kommerziell), gleiche API beibehalten.
- **MehrseitenHandling**: Seitenliste/Range, Animationserhalt für GIF/WebP.
- **Farbraum**: explizites ICCProfileHandling & Konvertierung.
---
## 12. Umsetzungsfahrplan (für Codex)
1. **Enum** `OutputImageFormat` anlegen (PNG/JPEG/GIF/BMP/TIFF + optional WEBP/JP2).
2. **Service** `ImageConversionService` implementieren (ReaderBufferedImageWriter; Quality nur wenn `canWriteCompressed`).
3. **Negotiator** `ImageFormatNegotiatorService` implementieren (SPIDetection via `ImageReaderSpi`: Namen/Suffixe/MIMEs; PassThrough vs. Convert).
4. **Controller** (optional) + DTOs + Validation.
5. **POM**: TwelveMonkeysModule + optional WebP/JP2.
6. **Tests**: Unit + Integration + Beispielbilder.
7. **Observability**: MicrometerTimer & Logs.
8. **Dockerfile** (Linux), optional WindowsImage; K8sManifestSnippets (Resources/Limits/Probes).
---
## 13. AkzeptanzCheckliste (DoD)
-
---
## 14. Anhang CodeSkeletons (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
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<version>${twelvemonkeys.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>${twelvemonkeys.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-psd</artifactId>
<version>${twelvemonkeys.version}</version>
</dependency>
<!-- optional: PNM/TGA/IFF/ICNS ... -->
<!-- optional JP2 -->
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<version>${jai.version}</version>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-jpeg2000</artifactId>
<version>${jai.version}</version>
</dependency>
<!-- optional WebP -->
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>${webp.version}</version>
</dependency>
```
**Build**
- `spring-boot-maven-plugin` `repackage` FatJAR.
- (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