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

12 KiB
Raw Permalink Blame History

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ändertes 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 JPEG→PNG 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

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, 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

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-pluginrepackage 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):

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 (Reader→BufferedImage→Writer; 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)

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-pluginrepackage FatJAR.
  • (Nur falls Shade) ServicesResourceTransformer zum Mergen der META-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 —