JPEG 图像颜色错误

发布于 2025-01-07 00:38:37 字数 1269 浏览 1 评论 0原文

我有一种读取图像、转换图像(大小、格式)并将其写回的方法。这一直很有效,但现在我遇到了一些 JPEG 图像(来自新闻机构),它们显然包含一些元数据 (IPTC)。转换这些图像时,颜色全部错误。我的第一个猜测是,这些是 CMYK 图像,但事实并非如此。

问题一定来自于阅读,因为无论我将图像转换为较小的JPEG还是PNG并不重要,它看起来总是一样的。

首先,我使用ImageIO.read()来读取图像。我现在通过 ImageIO.getImageReadersByMIMEType() 获取实际的 ImageReader ,并尝试通过设置 ignoreMetadata 参数来告诉读者忽略元数据code>ImageReader#setInput(Object input, booleaneekForwardOnly,booleanignoreMetadata) 但没有成功。

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

我能发现的唯一区别是,对于非工作图像,读取器变量 colorSpaceCode 的值为 2,而对于工作图像,该值为3。还有一个 outColorSpaceCode,这两个图像的值都是 2

作为读者的源注释< /a> 仅表示由 setImageData 本机代码回调设置。修改后的 IJG+NIFTY 色彩空间代码 我现在真的陷入困境了。因此,任何帮助将不胜感激。

您可以前往此处并点击下载来获取原始图片 (~3 MB)。下面的左图显示了我从原始图像中得到的内容,右图显示了它应该是什么样子。

错误的颜色 正确的颜色(删除元数据后)

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

荒路情人 2025-01-14 00:38:37

我现在找到了一个可行的解决方案,至少如果我生成的图像也是 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 图像,我仍然会得到错误的颜色(即使传递元数据),但我可以忍受。

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.

日记撕了你也走了 2025-01-14 00:38:37

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

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

而不是

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

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));
冬天的雪花 2025-01-14 00:38:37

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

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

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


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


む无字情书 2025-01-14 00:38:37

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

从字面上看,我要做的就是将其包含在我的 Maven 中依赖项和以奇怪颜色出现的 jpeg 开始正常读取。我什至不必更改一行代码。

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.

盗琴音 2025-01-14 00:38:37

当我尝试将图像从字节数组转换为 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);

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);
み青杉依旧 2025-01-14 00:38:37

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

  • 读取
  • 写入(将 ARGB 而不是 RGB 传递给 ImageIO.write() 时)
  • 渲染(来自操作系统、浏览器等的照片查看器应用程序)

我最近遇到的情况是上传 Greenshot 创建的 png 屏幕截图时,使用 ImageIO 读取,缩放然后用ImageIO as 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);
    }
  }

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);
    }
  }
自我难过 2025-01-14 00:38:37

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

如果有人找到一种方法来检测图像是否会渲染不良(除了目视),请告诉我们。 (比如,我从哪里获得这个所谓的 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());
    }

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());
    }
墨小墨 2025-01-14 00:38:37

这里看起来不错:

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.sstatic.net/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));
    }
}

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.sstatic.net/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));
    }
}
ぶ宁プ宁ぶ 2025-01-14 00:38:37
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));
    }
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));
    }
忆悲凉 2025-01-14 00:38:37

当原始 jpg 以 CMYK 颜色保存时会出现此问题。在 fe Paint 中将其重新着色为 RGB 彩色图像

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文