JPEG image with wrong colors
Asked Answered
G

10

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)

Groark answered 18/2, 2012 at 10:58 Comment(2)
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: chan.sankakustatic.com/data/cd/81/… 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.Spatterdash
Possible overlap: Image changes color when saved with JavaInquisitive
G
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.

Groark answered 18/2, 2012 at 13:22 Comment(0)
W
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));
Wast answered 18/6, 2012 at 13:8 Comment(0)
T
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. :)


Trough answered 18/9, 2012 at 2:14 Comment(1)
This answer is useless without code showing how to handle the conversion.Masturbation
P
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.

Peloria answered 18/2, 2016 at 18:33 Comment(0)
C
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);
Cordey answered 28/11, 2018 at 12:1 Comment(0)
N
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://mcmap.net/q/176648/-issue-using-imageio-write-jpg-file-pink-background-closed
          // https://mcmap.net/q/176193/-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);
    }
  }
Nascent answered 23/11, 2019 at 10:32 Comment(1)
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...Dreary
S
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());
    }
Spatterdash answered 22/2, 2012 at 13:16 Comment(2)
I believe the answer to your question of how to detect the bad JPEGs is found here and here. 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.Affirmative
You can adapt the partial JPEG decoder in this answer to detect the conversion condition.Affirmative
J
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));
    }
}
Joke answered 18/2, 2012 at 12:6 Comment(4)
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.Groark
"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).Joke
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).Groark
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.Groark
D
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));
    }
Doug answered 23/4, 2020 at 6:30 Comment(0)
T
-1

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

Twentyfourmo answered 10/8, 2022 at 7:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.