Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPEG image with wrong colors

Tags:

java

image

jpeg

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 colorscorrect colors (after removing metadata)

like image 342
Ridcully Avatar asked Feb 18 '12 10:02

Ridcully


7 Answers

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.

like image 150
Ridcully Avatar answered Oct 05 '22 18:10

Ridcully


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));
like image 30
ludovic Avatar answered Oct 05 '22 17:10

ludovic


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


like image 35
wbudic Avatar answered Oct 05 '22 19:10

wbudic


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.

like image 37
James Fiala Avatar answered Oct 05 '22 17:10

James Fiala


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);
like image 25
Kasun Avatar answered Oct 05 '22 17:10

Kasun


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);
    }
  }
like image 43
Reto Höhener Avatar answered Oct 05 '22 19:10

Reto Höhener


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());
    }
like image 34
Mark Jeronimus Avatar answered Oct 05 '22 17:10

Mark Jeronimus