JPEG-Bild mit falschen Farben

Lesezeit: 7 Minuten

JPEG Bild mit falschen Farben
Lächerlich

Ich habe eine Methode, die Bilder liest, konvertiert (Größe, Format) und zurückschreibt. Das hat immer sehr gut funktioniert, aber jetzt bin ich auf einige JPEG-Bilder (von einer Presseagentur) gestoßen, die offensichtlich einige Metadaten (IPTC) enthalten. Beim Konvertieren dieser Bilder sind die Farben alle falsch. Meine erste Vermutung war, dass das CMYK-Bilder sind, aber das sind sie nicht.

Das Problem muss vom Einlesen kommen, denn egal ob ich das Bild in ein kleineres JPEG oder ein PNG konvertiere, es sieht immer gleich aus.

Anfangs habe ich verwendet ImageIO.read() um das Bild zu lesen. Ich bekomme jetzt das Eigentliche ImageReader über ImageIO.getImageReadersByMIMEType() und versucht, dem Leser zu sagen, dass er Metadaten ignorieren soll, indem er die ignoreMetadata Parameter von ImageReader#setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) hatte aber keinen erfolg.

Dann habe ich eine Version des Bildes ohne die Metadaten erstellt (mit Fireworks). Dieses Bild wird korrekt konvertiert.

Der einzige Unterschied, den ich feststellen konnte, ist, dass mit dem nicht funktionierenden Bild der Wert der Variablen des Lesers colorSpaceCode ist 2während beim Arbeitsbild der Wert ist 3. Es gibt auch eine outColorSpaceCode welches ist 2 für beide Bilder.

Als die Quellenangabe des Lesers sagt nur Festgelegt durch SetImageData nativen Code-Callback. Ein modifizierter IJG+NIFTY-Farbraumcode Ich stecke jetzt wirklich fest. Also jede Hilfe wäre sehr willkommen.

Sie können das Originalbild (~3 MB) erhalten, indem Sie gehen Hier und auf Herunterladen klicken. Das linke Bild unten zeigt, was ich aus dem Originalbild bekomme, das rechte zeigt, wie es aussehen sollte.

falsche Farben
Korrekte Farben (nach dem Entfernen von Metadaten)

  • Ich habe dieses Problem seit ich denken kann. Es passiert in etwa 0,1 % der JPG-Dateien, denen ich begegne. Zum Beispiel: chan.sankakustatic.com/data/cd/81/… Ich habe noch keine Lösung gefunden, um sie in einem Panel korrekt anzuzeigen. Meine Vermutung ist, dass es sich um einen Fehler im JPEG-Parser von Java handelt.

    – Markus Hieronymus

    22. Februar 2012 um 9:57 Uhr


  • Mögliche Überlappung: Bild ändert Farbe, wenn es mit Java gespeichert wird

    – Franz Ebner

    26. Dezember 2013 um 18:25 Uhr


Ich habe jetzt eine Lösung gefunden, die funktioniert, zumindest wenn mein resultierendes Bild auch ein JPEG ist: Zuerst lese ich das Bild (aus dem Byte-Array imageData), und vor allem lese ich auch die Metadaten.

InputStream is = new BufferedInputStream(new ByteArrayInputStream(imageData));
Image src = null;
Iterator<ImageReader> it = ImageIO.getImageReadersByMIMEType("image/jpeg");
ImageReader reader = it.next();
ImageInputStream iis = ImageIO.createImageInputStream(is);
reader.setInput(iis, false, false);
src = reader.read(0);
IIOMetadata imageMetadata = reader.getImageMetadata(0);

Jetzt würde ich etwas konvertieren (dh verkleinern) … und schließlich würde ich das Ergebnis als JPEG-Bild zurückschreiben. Hier ist es am wichtigsten, die Metadaten, die wir vom Originalbild erhalten haben, an das neue weiterzugeben IIOImage.

Iterator<ImageWriter> iter = ImageIO.getImageWritersByMIMEType("image/jpeg");
ImageWriter writer = iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(jpegQuality);
ImageOutputStream imgOut = new MemoryCacheImageOutputStream(out);
writer.setOutput(imgOut);
IIOImage image = new IIOImage(destImage, null, imageMetadata);
writer.write(null, image, iwp);
writer.dispose();

Wenn ich ein PNG-Bild schreiben würde, bekomme ich leider immer noch die falschen Farben (auch wenn die Metadaten weitergegeben werden), aber damit kann ich leben.

Ich hatte ein ähnliches Problem. Ich musste verwenden:

Image image = java.awt.Toolkit.getDefaultToolkit().getImage(path);

anstatt

Image image = javax.imageio.ImageIO.read(new File(path));

1647158830 876 JPEG Bild mit falschen Farben
wbudic

Ich hatte ähnliche Probleme, die BufferedImage Zurückgegeben wird eine Wiedergabe, die darauf basiert, ob transparente Pixel vorhanden sind, was für die meisten PNG/GIF-Dateien auf „true“ gesetzt wird. Beim Konvertieren in JPEG sollte dieses Flag jedoch auf “false” gesetzt werden. Möglicherweise müssen Sie eine Methode schreiben, in der die Konvertierung ordnungsgemäß durchgeführt wird. dh:

public static BufferedImage toBufferedImage(Image image) {
...
}

Andernfalls wird dieser “marunische” Oberton zum gespeicherten Ergebnis. 🙂


  • Diese Antwort ist nutzlos ohne Code, der zeigt, wie die Konvertierung gehandhabt wird.

    – Ali

    29. Oktober 2013 um 13:21 Uhr

Ich bin auf dieses Problem gestoßen und habe tatsächlich eine Bibliothek eines Drittanbieters gefunden, die dies für mich erledigt hat. https://github.com/haraldk/TwelveMonkeys

Alles, was ich buchstäblich tun musste, war, dies in meine Maven-Abhängigkeiten aufzunehmen, und die JPEGs, die in seltsamen Farben herauskamen, wurden normal eingelesen. Ich musste nicht einmal eine Codezeile ändern.

1647158831 451 JPEG Bild mit falschen Farben
Kasun

Ich hatte ein ähnliches Problem, als ich versuchte, ein Bild von einem Byte-Array in Base64 zu konvertieren. Es scheint, dass das Problem durch Bilder mit einem Alphakanal verursacht wird. Beim Speichern eines Bildes mit Alphakanal wird auch der Alphakanal gespeichert und einige externe Programme, die zum Lesen des Bildes verwendet werden, interpretieren die 4 Kanäle als CMYK.

Es wurde eine einfache Problemumgehung gefunden, indem der Alphakanal des BufferedImage entfernt wurde. Das mag dumm sein, aber es hat auf jeden Fall für mich funktioniert.

//Read the image from a byte array
BufferedImage bImage = ImageIO.read(new ByteArrayInputStream(byteArray));

//Get the height and width of the image
int width = bImage.getWidth();
int height = bImage.getHeight();

//Get the pixels of the image to an int array 
int [] pixels=bImage.getRGB(0, 0,width,height,null,0,width);

//Create a new buffered image without an alpha channel. (TYPE_INT_RGB)
BufferedImage copy = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);

//Set the pixels of the original image to the new image
copy.setRGB(0, 0,width,height,pixels,0,width);

Beachten Sie, dass Probleme in verschiedenen Phasen auftreten können:

  • lesen
  • Schreiben (bei Übergabe von ARGB statt RGB an ImageIO.write())
  • Rendering (Foto-Viewer-App von OS, Browser etc.)

Meine letzte Begegnung damit war beim Hochladen png Screenshots erstellt von Grünschusslesen mit ImageIOskalieren und dann mit schreiben ImageIO als jpeg (Ihr typischer Thumbnail-Prozess).

Meine Lösung für die Schreibseite: Entfernen Sie den Alphakanal, um zu vermeiden, dass Browser das Bild als YMCK interpretieren):

public static byte[] imageToBytes(BufferedImage image, String format) {
    try {
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      BufferedImage imageToWrite = image;
      if(format.toLowerCase().endsWith("jpg") || format.toLowerCase().endsWith("jpeg")) {
        if(image.getType() != BufferedImage.TYPE_INT_RGB) {
          // most incoming BufferedImage that went through some ImageTools operation are ARGB
          // saving ARGB to jpeg will not fail, but e.g. browser will interpret the 4 channel images as CMYK color or something
          // need to convert to RGB 3-channel before saving as JPG
          // https://stackoverflow.com/a/46460009/1124509
          // https://stackoverflow.com/questions/9340569/jpeg-image-with-wrong-colors

          // if the reading already produces wrong colors, also try installing twelvemonkeys image plugin (for better jpeg reading support)
          // https://github.com/haraldk/TwelveMonkeys
          // ImageIO.scanForPlugins();
          // GT.toList(ImageIO.getImageReadersByFormatName("jpeg")).forEach(i -> System.out.println(i));
          int w = image.getWidth();
          int h = image.getHeight();
          imageToWrite = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
          int[] rgb = image.getRGB(0, 0, w, h, null, 0, w);
          imageToWrite.setRGB(0, 0, w, h, rgb, 0, w);
        }
      }
      ImageIO.write(imageToWrite, format, byteArrayOutputStream);
      byte[] bytes = byteArrayOutputStream.toByteArray();
      return bytes;
    }
    catch(Exception e) {
      throw new RuntimeException(e);
    }
  }

1647158831 663 JPEG Bild mit falschen Farben
Markus Hieronymus

Hier ist ein Algorithmus, um das “schlechte” Bild in ein gutes umzuwandeln, aber ich habe keine Möglichkeit gefunden, automatisch zu erkennen, ob ein Bild schlecht gerendert wird, also ist es immer noch nutzlos.

Wenn jemand einen Weg findet, um festzustellen, ob ein Bild schlecht gerendert wird (abgesehen von Augeballen), teilen Sie uns dies bitte mit. (wie, wo bekomme ich diese sog colorSpaceCode Wert?!)

    private static void fixBadJPEG(BufferedImage img)
    {
        int[] ary = new int[img.getWidth() * img.getHeight()];
        img.getRGB(0, 0, img.getWidth(), img.getHeight(), ary, 0, img.getWidth());
        for (int i = ary.length - 1; i >= 0; i--)
        {
            int y = ary[i] >> 16 & 0xFF; // Y
            int b = (ary[i] >> 8 & 0xFF) - 128; // Pb
            int r = (ary[i] & 0xFF) - 128; // Pr

            int g = (y << 8) + -88 * b + -183 * r >> 8; //
            b = (y << 8) + 454 * b >> 8;
            r = (y << 8) + 359 * r >> 8;

            if (r > 255)
                r = 255;
            else if (r < 0) r = 0;
            if (g > 255)
                g = 255;
            else if (g < 0) g = 0;
            if (b > 255)
                b = 255;
            else if (b < 0) b = 0;

            ary[i] = 0xFF000000 | (r << 8 | g) << 8 | b;
        }
        img.setRGB(0, 0, img.getWidth(), img.getHeight(), ary, 0, img.getWidth());
    }

  • Ich glaube, die Antwort auf Ihre Frage, wie man die schlechten JPEGs erkennt, ist gefunden Hier und hier. Was Sie haben, ist ein JPEG ohne JFIF-Marker. Alle anderen Bildladeprogramme gehen in diesem Fall davon aus, dass es sich bei den Daten um YCbCr handelt, mit Ausnahme von ImageIO, das davon ausgeht, dass es sich um RGB handelt, wenn die Kanäle 1 und 2 nicht unterabgetastet werden. Überprüfen Sie also, ob die ersten 4 Bytes FF D8 FF E1 sind, und wenn ja, ob die Kanäle 1 und 2 unterabgetastet sind. Das ist der Fall, wo Sie konvertieren müssen.

    – uckelmann

    24. Februar 2013 um 19:14 Uhr

  • Sie können den partiellen JPEG-Decoder in dieser Antwort anpassen, um die Konvertierungsbedingung zu erkennen.

    – uckelmann

    24. Februar 2013 um 20:33 Uhr

996550cookie-checkJPEG-Bild mit falschen Farben

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy