JPEG图像颜色错误

时间:2012-02-18 10:58:49

标签: java image jpeg

我有一个方法可以读取图像,转换它们(大小,格式)并将它们写回。这总是很好用,但现在我遇到了一些显然包含一些元数据(IPTC)的JPEG图像(来自新闻社)。转换这些图像时,颜色都是错误的。我的第一个猜测是,那些是CMYK图像,但它们不是。

问题必须来自阅读,因为无论我是将图像转换为较小的JPEG还是PNG都无关紧要,它看起来总是一样。

首先,我使用ImageIO.read()来阅读图片。我现在通过ImageReader获取实际的ImageIO.getImageReadersByMIMEType()并尝试通过设置ignoreMetadata的{​​{1}}参数来告诉读者忽略元数据,但没有成功。

然后我创建了一个没有元数据的图像版本(使用Fireworks)。该图像转换正确。

我能找到的唯一区别是,对于未工作的图像,读者变量ImageReader#setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata)的值为 2 ,而对于工作图像,值为 3 即可。两张图片还有colorSpaceCode 2

由于source comment of the reader仅表示由setImageData本机代码回调设置。修改后的IJG + NIFTY颜色空间代码我现在真的被困住了。所以任何帮助都会非常感激。

您可以通过here并点击下载来获取原始图像(~3 MB)。下图左侧显示了我从原始图像中获得的内容,右侧显示了它应该是什么样子。

wrong colors correct colors (after removing metadata)

9 个答案:

答案 0 :(得分:9)

我现在找到了一个解决方案,至少在我生成的图像也是JPEG的情况下是有效的: 首先我读取图像(来自字节数组imageData),最重要的是,我还读取了元数据。

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

现在我要进行一些转换(即缩小尺寸)......最后我将结果写回JPEG图像。最重要的是将我们从原始图像获得的元数据传递给新的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();

不幸的是,如果我写一个PNG图像,我仍然会得到错误的颜色(即使传递元数据),但我可以忍受。

答案 1 :(得分:5)

我有类似的问题,如果有透明像素,返回的BufferedImage是基于再现的,对于大多数png / gif类型的文件将设置为true。但是当转换为jpeg时,此标志应设置为false。您可能需要编写一个方法,正确处理转换。即:

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

否则“marunish”泛音成为保存的结果。 :)


答案 2 :(得分:4)

我遇到了类似的问题。我不得不使用:

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

而不是

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

答案 3 :(得分:3)

我遇到了这个问题,我实际上找到了一个为我处理这个问题的第三方库。 https://github.com/haraldk/TwelveMonkeys

字面上,我所要做的就是在我的maven依赖项中包含这个,并且以奇怪的颜色出现的jpegs开始正常读取。我甚至不必改变一行代码。

答案 4 :(得分:2)

这是一种将“坏”图像转换为好图像的算法,但是,我还没有找到任何方法来自动检测图像是否会被渲染得很糟糕,所以它仍然没用。

如果有人找到方法来检测图像是否会变坏(除了眼球),请告诉我们。 (比如,我在哪里得到这个所谓的colorSpaceCode值?!)

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

答案 5 :(得分:0)

这里似乎很好:

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("http://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));
    }
}

答案 6 :(得分:0)

当尝试将图像从字节数组转换为Base64时,我遇到了类似的问题。看来问题是由带有Alpha通道的图像引起的。当使用Alpha通道保存图像时,也会保存Alpha通道,并且一些用于读取图像的外部程序会将4个通道解释为CMYK。

通过删除BufferedImage的alpha通道找到了一个简单的解决方法。这可能很愚蠢,但对我确实有用。

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

答案 7 :(得分:0)

请注意,问题可能会发生在各个阶段:

  • 阅读
  • 写(将ARGB而不是RGB传递给ImageIO.write()时)
  • 渲染(来自操作系统,浏览器等的foto viewer应用)

我最近遇到的一个问题是上传Greenshot创建的png屏幕截图,使用ImageIO阅读,缩放,然后使用ImageIO作为jpeg进行书写(您通常的缩略图过程)。

我在书写方面的解决方案:删除Alpha通道,以避免浏览器将图像解释为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);
    }
  }

答案 8 :(得分: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));
    }