Speichern von Image als JPG auf Festplatte

MiMa

Top Contributor
Aktuell habe ich ein Problem welches ich schon seit einger Zeit versuche zu lösen aber nicht mehr weiter weis.
In einer Variable bild vom Typ Image liegt der Inhalt im Speicher.
Java:
public static void speichernBild(Image bild, String dateiname) {
        try {
            BufferedImage bufferedImage = SwingFXUtils.fromFXImage(bild, null);
            if (bufferedImage != null)
                System.out.println("Das Buffered Image ist nicht leer");

            File output = new File(dateiname);
            System.out.println("Der Dateiname : " + dateiname);
            ImageIO.write(bufferedImage, "jpg", output);

            if (new File(dateiname).exists()){
                System.out.println("Das Bild wurde gespeichert : " + dateiname);
            } else{
                System.out.println("Das Bild wurde nicht gespeichert");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    } // speichernBild

Das Bild wird auf Festplatte gespeichert und kann es mir auch ansehen.

Jetzt habe ich eine weitere Methode, die Bilder Skallieren soll
Code:
public static Image skaliereBild(Image bild, int maximaleBreite, int maximaleHöhe) {
        int ursprünglicheBreite = (int) bild.getWidth();
        int ursprünglicheHöhe = (int) bild.getHeight();

        // Überprüfen, ob eine Skalierung erforderlich ist
        if (ursprünglicheBreite <= maximaleBreite && ursprünglicheHöhe <= maximaleHöhe) {
            return bild; // Keine Skalierung erforderlich
        }

        // Berechnen der Skalierungsfaktoren
        double faktorBreite = (double) maximaleBreite / ursprünglicheBreite;
        double faktorHöhe = (double) maximaleHöhe / ursprünglicheHöhe;
        double skalierungsfaktor = Math.min(faktorBreite, faktorHöhe);

        // Skalieren des Bildes
        int neueBreite = (int) (ursprünglicheBreite * skalierungsfaktor);
        int neueHöhe = (int) (ursprünglicheHöhe * skalierungsfaktor);

        BufferedImage skaliertesBufferedImage = new BufferedImage(neueBreite, neueHöhe, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = skaliertesBufferedImage.createGraphics();
        g2d.drawImage(SwingFXUtils.fromFXImage(bild, null), 0, 0, neueBreite, neueHöhe, null);
        g2d.dispose();

        return SwingFXUtils.toFXImage(skaliertesBufferedImage, null);
    }

Wenn ich ein Bild habe welches im Original 1200x1200 Pixel, gross ist und ich die Methode wie folgt aufrufe

Code:
Image skaliertesBild = Bild.skaliereBild(coverBild, 600, 600);
if (skaliertesBild != null) {
                System.out.println("Das Skallierte Bild ist nicht null");
                int breite = (int) skaliertesBild.getWidth();
                int hoehe = (int) skaliertesBild.getHeight();
                System.out.println("Das Bild hat die Größe: " + breite + "x" + hoehe);

            } else {
                System.out.println("Das Skallierte Bild ist null");
            }

Erhalte ich folgendes in der Konsole
Das Skallierte Bild ist nicht null
Das Bild hat die Größe: 600x600
Das Buffered Image ist nicht leer
Das Bild wurde gespeichert : D:\temp\CoverBild_skaliert.jpg
Allerdings wird die gespeicherte Datei nirgends auf der Festplatte gefunden.
Ich vermute, das es nicht geschrieben wurde. Dann habe ich die Speichern Methode erweitert mit der existenzprüfung und habe richtig vermutet, das Bild wurde nicht geschrieben.
Ein Bild welches unter 600x600 liegt wie 500x500, wurde von der Skallieren methode nicht verändert und unbearbeitet zurück gegeben. Dieses wurde auch ordnungsgemäss auf Festplatte gespeichert. Bilder die grösser sind als 600x600 wurden skalliert, und laut Konsolenausgabe sind diese nicht null oder leer und haben auch die richtige Breite und Höhe 600x600 aber lassen sich irgendwie nicht speichern???

Hat jemand eine Idee woran das liegen kann?
Mein Projekt ist meine Methoden-Bibliothek welches JavaFX 20 enthält aber keine grafische Oberfläche.
In diesem Projekt schreibe ich hauptsächlich Methoden die ohne Oberfläche arbeiten.

Vielen Dank
 
Zuletzt bearbeitet:
Also prinzipiell sieht das, was du an Code zeigst, korrekt aus.

Aber du zeigst nicht den genauen Code, der die Meldungen gebracht hat. Evtl. zeigst Du den genauen Code, der das skalierte Bild dann speichert. Dein Code endet mit der Ausgabe der Größe und du zeigst nicht den Code, der die weiteren Meldungen anzeigt. Evtl. hast Du irgend eine Abweichung zu dem Code zum Speichern, den Du zuerst gezeigt hast.

Was zu dem Code noch auffällt: Die write Methode gibt ein boolean zurück. Das solltest Du auch prüfen. Wenn da false zurück gegeben wurde, dann bedeutet das, dass kein passender Writer gefunden wurde. Das könnte meines Wissens nach der Fall sein, wenn Du Dich beim Typ vertippt haben solltest.
 
ImageIO.write hat eine Rückgabe: false if no appropriate writer is found.
Hast du diese schon mal ausgewertet?

String writerNames[] = ImageIO.getWriterFormatNames();
Gibt dir die möglichen Bildformate für dein sytem zurück.
 
@Oneixee5
Ja habe geprüft und daran dürfte es nicht liegen.

@KonradN
weiss nicht welchen genauen Code du meinst?
Hier ist der Code um die Methoden auf zu rufen. Dabei wird das Image aus eine MP3 Datei extrahiert und weiterverarbeitet.
Java:
Image coverBild = metaDaten.getAlbumCover();
        if (coverBild != null) {
            // Speichere das Originalbild
            Bild.speichernBild(coverBild, metaDaten.getQuelle().getParent() + File.separator + metaDaten.getNameOhneEndung() + "_org.jpg");

            // Skaliere das Bild
            Image skaliertesBild = Bild.skaliereBild(coverBild, 600, 600);

            if (skaliertesBild != null) {
                System.out.println("Das Skalierte Bild ist nicht null");
                int breite = (int) skaliertesBild.getWidth();
                int hoehe = (int) skaliertesBild.getHeight();
                System.out.println("Das Bild hat die Größe: " + breite + "x" + hoehe);

            } else {
                System.out.println("Das Skalierte Bild ist null");
            }

            // Speichere das skalierte Bild
            Bild.speichernBild(skaliertesBild, metaDaten.getQuelle().getParent() + File.separator + metaDaten.getNameOhneEndung() + "_skaliert.jpg");
        } else {
            System.out.println("Fehler beim Laden des Bildes.");
        }

Das ist die Ausgabe in der Konsole
Das Buffered Image ist nicht leer
Der Dateiname : D:\DMS\01 Postfach\1-01 Sweet But Psycho_org.jpg (Orginalbild ohne Skalierung)
Das Bild wurde gespeichert : D:\DMS\01 Postfach\1-01 Sweet But Psycho_org.jpg
Das Skallierte Bild ist nicht null
Das Bild hat die Größe: 600x600
Das Buffered Image ist nicht leer
Der Dateiname : D:\DMS\01 Postfach\1-01 Sweet But Psycho_skaliert.jpg (Dateiname für das Skalierte Bild)
Das Bild wurde nicht gespeichert
 
Ich habe es mir jetzt etwas angeschaut und das Problem dürfte das BufferedImage sein, welches zurück gegeben wird. Du kannst einmal beim Speichern ausgeben lassen:

Java:
            System.out.println(bufferedImage.getColorModel());
            System.out.println(bufferedImage.getType());

Ebenso solltest Du den Rückgabewert vom write Aufruf auswerten. Der müsste bei Dir auch false sein.

Leider nur Vermutungen, da ich jetzt keine Zeit mehr habe, es zu verifizieren:

Das Problem wird sein, dass SwingFXUtils.fromFXImage ein Ergebnis liefert, das auch einen Alpha Kanal hat. JPEG unterstützt keinen Alpha Kanal.

Mögliche Lösungen:
  • Statt jpg gibst Du ein Format an, das auch einen Alpha Kanal unterstützt. Das könnte z.B. PNG sein.
  • Du kannst das Ziel BufferedImage vorab erzeugen (z.B. als BufferedImage.TYPE_INT_RGB) und dann als zweiten Parameter im fromFXImage Aufruf übergeben. Dann hast Du ein BufferedImage one Alpha Kanal und kannst dieses dann als JPG speichern.

Aber wie gesagt: Noch reine Vermutung aber ich habe keine Zeit mehr es zu vertiefen.

Edit: Das fromFXImage kann alleine nicht das Problem sein, denn in meinem Test hatte ich ein Image geladen und da war noch TYPE_INT_RGB. Aber beim Scaling mit einem TYPE_INT_RGB BufferedImage und dann dem toFXImage / fromFXImage ist es kein TYPE_INT_RGB - da veränderte sich bei mir der Typ.
 
Zuletzt bearbeitet:
Jetzt konnte ich doch nicht abbrechen - es gab in der vorherigen Antwort ein Edit, denn mir war halt eingefallen, dass mein Verdacht nicht sein konnte. Und zuerst dachte ich, dass im Scaling ein falscher Typ beim BufferedImage angegeben worden wäre ... das war aber nicht der Fall.

Der mögliche Fix (Lösungsvorschlag, den ich schon gemacht habe) wäre damit etwas wie:
Java:
            BufferedImage newImage = new BufferedImage((int)bild.getWidth(), (int)bild.getHeight(), BufferedImage.TYPE_INT_RGB);
            BufferedImage bufferedImage = SwingFXUtils.fromFXImage(bild, newImage);
beim speichern des Bildes. Das hatte dann in meinen Tests funktioniert.
 
Vielen Dank, deine vermutung war richtig.
Ich habe die Zeile korrigiert
Java:
ImageIO.write(bufferedImage, "png", output);
und auch die Dateiendung beim speichern der Datei (png) angepasst.
Jetzt werden die beiden Bilddateien korrekt gespeichert.
Einmal das mit der Originalgrösse und das Skalierte.

Das mit dem jpg werde ich aber noch versuchen hin zu bekommen.
 
Ich habe jetzt die Speichern Methode umgeschrieben und jetzt funktioniert das speichern auch im jpg Format.

Java:
public static void speichernBildJPEG(Image bild, String dateiname) throws IOException {
        BufferedImage bufferedImageWithAlpha = SwingFXUtils.fromFXImage(bild, null);

        // BufferedImage ohne Alphakanal erstellen
        BufferedImage bufferedImageWithoutAlpha = new BufferedImage(bufferedImageWithAlpha.getWidth(),
                bufferedImageWithAlpha.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = bufferedImageWithoutAlpha.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.drawImage(bufferedImageWithAlpha, 0, 0, null);
        g2d.dispose();

        // Als JPEG speichern
        File ausgabeDatei = new File(dateiname + ".jpg");
        ImageIO.write(bufferedImageWithoutAlpha, "jpg", ausgabeDatei);
    } // speichernBildJPEG

Vielen Dank für die Hilfe
 
Damit wird das Bild doppelt verarbeitet:
  • erst wird in dem fromFXImage Aufruf das Bild kopiert
  • dann kopierst Du es ein zweites Mal mit dem Graphics2D im draw aufruf.

In #6 hatte ich Dir aufgezeigt, wie Du es nur einmal verarbeitest: Erzeugung der gewünschten BufferedImage Instance und dann Aufruf von fromFXImage mit dieser Bitmap als Parameter. (Und falls Du viele Bilder verarbeiten solltest: Der Speicherverbrauch ist doppelt so hoch, da Du zwei BufferedImage Instanzen hast. in #6 habe ich zwar zwei Variablen, aber die BufferedImage Instanz ist die selbe).
 
Danke dir, hab es korrigiert und funktioniert gut.

Java:
public static void speichernBildJPG(Image bild, String dateiname) throws IOException {
        // Ein BufferedImage ohne Alphakanal direkt erstellen und mit FX Image rendern
        BufferedImage bildOhneAlpha = new BufferedImage((int) bild.getWidth(), (int) bild.getHeight(), BufferedImage.TYPE_INT_RGB);
        SwingFXUtils.fromFXImage(bild, bildOhneAlpha);

        // Als JPEG speichern
        File ausgabeDatei = new File(dateiname + ".jpg");
        ImageIO.write(bildOhneAlpha, "jpg", ausgabeDatei);
    } // speichernBildJPG
 

Zurück
Oben