34

I have a method that reads images, converts them (size, format) and writes them back. This always worked very well, but now I've come across some JPEG images (from a Press Agency) that obviously contain some meta-data (IPTC). When converting those images, the colors are all wrong. My first guess was, that those are CMYK images but they are not.

The problem must come from the reading, because it doesn't matter whether I convert the image to a smaller JPEG or a PNG, it always looks the same.

At first, I used ImageIO.read() to read the image. I now get the actual ImageReader via ImageIO.getImageReadersByMIMEType() and tried to tell the reader to ignore meta data by setting the ignoreMetadata parameter of ImageReader#setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) but had no success.

Then I created a version of the image without the metadata (using Fireworks). That image is converted correctly.

The only difference I could find out, is, that with the not-working image the value of the reader's variable colorSpaceCode is 2, whilest with the working image, the value is 3. There's also an outColorSpaceCode which is 2 for both images.

As the source comment of the reader only says Set by setImageData native code callback. A modified IJG+NIFTY colorspace code I'm really stuck now. So any help would be much appreciated.

You can get original image (~3 MB) by going here and clicking download. The left image below shows what I get from the original image, the right shows what it should look like.

wrong colors correct colors (after removing metadata)

fabian
  • 80,457
  • 12
  • 86
  • 114
Ridcully
  • 23,362
  • 7
  • 71
  • 86
  • I've had this problem for as long as I can remember. It happens in about 0.1% of the jpg files I encounter. For example: http://chan.sankakustatic.com/data/cd/81/cd81a9fa1305b9c1887ab1ac4904d166.jpg I haven't found a solution yet for displaying them correctly in a panel. My guess is it's a bug in Java's JPEG parser. – Mark Jeronimus Feb 22 '12 at 09:57
  • Possible overlap: [Image changes color when saved with Java](http://stackoverflow.com/questions/20789043/image-changes-color-when-saved-with-java) – Franz Ebner Dec 26 '13 at 18:25

10 Answers10

10

I found a solution now, that works, at least if my resulting image is also a JPEG: First I read the image (from byte array imageData), and most important, I also read the metadata.

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);

Now i'd do some converting (i.e. shrink in size) ... and at last I'd write the result back as a JPEG image. Here it is most important to pass the metadata we got from the original image to the new 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();

Unfortunately, if I'd write a PNG image, I still get the wrong colors (even if passing the metadata), but I can live with that.

Ridcully
  • 23,362
  • 7
  • 71
  • 86
4

I had a similar problem.I had to use:

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

instead of

Image image = javax.imageio.ImageIO.read(new File(path));
ludovic
  • 41
  • 2
4

I had similar problems, the BufferedImage returned is a rendition based if there is transparent pixel, which will be set true for most png/gif type of files. But when converting to jpeg this flag should be set to false. You need possibly to write a method, where the conversion is properly handled. i.e.:

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

Otherwise that "marunish" overtone becomes the saved result. :)


Taryn East
  • 27,486
  • 9
  • 86
  • 108
wbudic
  • 63
  • 1
  • 2
4

I was running into this issue, and I actually found a third party library that handled this for me. https://github.com/haraldk/TwelveMonkeys

Literally all I had to do was include this in my maven dependencies and the jpegs that were coming out in weird colors started getting read in normally. I didn't even have to change a line of code.

James Fiala
  • 311
  • 4
  • 12
3

I had a similar problem when trying to convert an image from a byte array to Base64. It appears the problem is caused by images with an alpha channel. When saving an image with an alpha channel, the alpha channel is saved too and some external programs that are used to read the image interpret the 4 channels as CMYK.

Found a simple workaround, by removing the alpha channel of the BufferedImage. This may be stupid but it sure worked for me.

//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);
Kasun
  • 31
  • 2
3

Note that problems can happen at various stages:

  • reading
  • writing (when passing ARGB instead of RGB to ImageIO.write())
  • rendering (foto viewer app from OS, browser etc.)

My latest encounter with this was when uploading png screenshots created by Greenshot, reading with ImageIO, scaling and then writing with ImageIO as jpeg (your typical thumbnail process).

My solution for the writing side: remove alpha channel to avoid browsers interpreting the image as YMCK):

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);
    }
  }
Reto Höhener
  • 5,419
  • 4
  • 39
  • 79
  • Hm, just noticed that I basically wrote the same answer as @Kasun. But maybe it's ok to draw more attention to this side of the problem... – Reto Höhener Nov 23 '19 at 10:36
2

Here's an algorithm to transform the 'bad' image into a good one, however, I have not found any way of automatically detecting if an image will be rendered badly, so it is still useless.

If anyone finds a way to detect if an image will be rendered bad (other than eyeballing), please tell us. (like, where do I get this so-called colorSpaceCode value?!)

    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());
    }
Mark Jeronimus
  • 9,278
  • 3
  • 37
  • 50
  • 9
    I believe the answer to your question of how to detect the bad JPEGs is found [here](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4712797) and [here](http://stackoverflow.com/questions/7676701/java-jpeg-converter-for-odd-image-types). What you have is a JPEG with no JFIF marker. All other image loaders assume that the data is YCbCr in that case, except for ImageIO, which assumes that it is RGB when channels 1 and 2 are not subsampled. So, check whether the first 4 bytes are FF D8 FF E1, and if so, whether channels 1 and 2 are subsampled. That's the case where you need to convert. – uckelman Feb 24 '13 at 19:14
  • You can adapt the partial JPEG decoder in [this answer](http://stackoverflow.com/questions/672916/how-to-get-image-height-and-width-using-java#2911772) to detect the conversion condition. – uckelman Feb 24 '13 at 20:33
0
public static void write_jpg_image(BufferedImage bad_image,String image_name) throws IOException {
        BufferedImage good_image=new BufferedImage(bad_image.getWidth(),bad_image.getHeight(),BufferedImage.TYPE_INT_RGB);
        Graphics2D B2G=good_image.createGraphics();
        B2G.drawImage(bad_image,0,0,null);
        B2G.dispose();
        ImageIO.write(good_image, "jpg", new File(image_name));
    }
linker
  • 821
  • 1
  • 8
  • 20
0

Seems fine here:

TestImage result

import java.awt.image.BufferedImage;
import java.net.URL;
import java.io.File;
import javax.imageio.ImageIO;

import javax.swing.*;

class TestImage {

    public static void main(String[] args) throws Exception {
        URL url = new URL("https://i.stack.imgur.com/6vy74.jpg");
        BufferedImage origImg = ImageIO.read(url);

        JOptionPane.showMessageDialog(null,new JLabel(new ImageIcon(origImg)));

        File newFile = new File("new.png");
        ImageIO.write(origImg, "png", newFile);
        BufferedImage newImg = ImageIO.read(newFile);

        JOptionPane.showMessageDialog(null,new JLabel(
            "New",
            new ImageIcon(newImg),
            SwingConstants.LEFT));
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • Well, the pictures are the results of my conversions, the right one is the one I get if I remove the metadata. I should have posted the original non-working image, but it is very large (about 3mb). I'll upload it somewhere and add a link to the question. Thanks for your code though, I'll try it with the original image. – Ridcully Feb 18 '12 at 12:23
  • *"but it is very large (about 3mb)."* Please find a smaller one that breaks. I cannot afford the bandwidth of experimenting with code that needs a 3 meg download (once or repeatedly). – Andrew Thompson Feb 18 '12 at 12:37
  • I tried uploading the original image to imgur.com but obviously they remove the meta-tags, because with the image uploaded there, the image looks ok. I've now uploaded the image to google-docs, from there you can actually download the original image and with your code, I get it with the wrong colors :-/ (had to change to code to read from local File instead of URL). – Ridcully Feb 18 '12 at 12:43
  • I got those images from our client (a newspaper) who got it from a Press Agency, so unfortunately I cannot ask for a smaller one. I'll try to find a tool so that I can add meta-data to a JPEG by myself. – Ridcully Feb 18 '12 at 12:46
-1

The problem occurs when the original jpg was saved in CMYK color. Recolor it as an RGB color image in f.e. Paint

Wenske
  • 1