12 KiB
12 KiB
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
negotiateAndConvert(byte[], allowed, preferred, quality)liefert:- Unverändertes Byte‑Array falls Quellformat∈allowed erkannt wird.
- Andernfalls konvertiert in
preferredunter Beachtung des Quality‑Hints (falls anwendbar).
- Erkennung des Quellformats ohne MIME‑Type ausschließlich über ImageIO/SPI.
- 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; optionalframeIndex&quality. - ``: nimmt
byte[]+allowedTargets+preferredTarget; nutzt ImageIO‑SPI, entscheidet Pass‑Through vs. Konvertierung. - (Optional) REST‑Controller: Multipart‑Endpoint
/convertund/negotiate.
4.2 Datenfluss
negotiateAndConverterstelltImageInputStreamaufbyte[].ImageIO.getImageReaders(iis)→ ersterImageReader+ImageReaderSpi→ Namen/Suffixe/MIMEs sammeln; Vergleich mitallowedTargets.- Wenn Match → Rückgabe
byte[]unverändert. Sonst →ImageConversionService.convert(..., preferred).
4.3 SPI & Plugins
- ImageIO‑SPI lädt Reader/Writer per
META-INF/servicesder 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(+ optionalWEBP,JP2). - Methoden:
extension(),mimeType(),imageIoFormatName(),fromExtension(String).
5.2 ImageConversionService
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
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, optionalquality)POST /negotiate(multipart:file,allowed=CSV,preferred, optionalquality)- Responses:
200 OKmitContent-Typegemäß Zielformat;415bei nicht unterstützten Quellen/Zielen.
5.5 CSV-Parser ImageFormatParsers
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→repackageFat‑JAR (Nested JARs inBOOT-INF/lib). - Ohne Shade empfohlen: SPI‑Files bleiben automatisch intakt.
- Falls Shade unbedingt nötig:
ServicesResourceTransformeraktivieren (mergeMETA-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, optionalimageio-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):
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
OutputImageFormatZiele (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)
- Enum
OutputImageFormatanlegen (PNG/JPEG/GIF/BMP/TIFF + optional WEBP/JP2). - Service
ImageConversionServiceimplementieren (Reader→BufferedImage→Writer; Quality nur wenncanWriteCompressed). - Negotiator
ImageFormatNegotiatorServiceimplementieren (SPI‑Detection viaImageReaderSpi: Namen/Suffixe/MIMEs; Pass‑Through vs. Convert). - Controller (optional) + DTOs + Validation.
- POM: TwelveMonkeys‑Module + optional WebP/JP2.
- Tests: Unit + Integration + Beispielbilder.
- Observability: Micrometer‑Timer & Logs.
- Dockerfile (Linux), optional Windows‑Image; K8s‑Manifest‑Snippets (Resources/Limits/Probes).
13. Akzeptanz‑Checkliste (DoD)
14. Anhang – Code‑Skeletons (Kurz)
Enum (Auszug)
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
<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→repackageFat‑JAR.- (Nur falls Shade)
ServicesResourceTransformerzum Mergen derMETA-INF/services.
Docker (Linux, Beispiel)
FROM eclipse-temurin:21-jre
WORKDIR /opt/app
COPY target/app.jar app.jar
ENTRYPOINT ["java","-jar","/opt/app/app.jar"]
— Ende —