web-dev-qa-db-ja.com

間違った色のJPEG画像

画像を読み取り、変換(サイズ、フォーマット)して、書き戻すメソッドがあります。これは常に非常にうまく機能しましたが、明らかにいくつかのメタデータ(IPTC)を含むいくつかのJPEG画像(通信社から)に出くわしました。それらの画像を変換するとき、色はすべて間違っています。私の最初の推測は、それらはCMYK画像ですが、そうではないということでした。

画像を小さいJPEGに変換するかPNGに変換するかは関係なく、常に同じように見えるため、問題は読み取りに起因する必要があります。

最初は、ImageIO.read()を使用して画像を読み取りました。 ImageIO.getImageReadersByMIMEType()を介して実際のImageReaderを取得し、ImageReader#setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata)ignoreMetadataパラメーターを設定してメタデータを無視するようにリーダーに指示しようとしましたが、成功。

次に、メタデータなしのバージョンの画像を作成しました(Fireworksを使用)。その画像は正しく変換されます。

私が見つけた唯一の違いは、機能していない画像では、リーダーの変数colorSpaceCodeの値が2であるということです。作業イメージの場合、値は3です。両方の画像に2であるoutColorSpaceCodeもあります。

読者のソースコメントsetImageDataネイティブコードコールバックによって設定されるだけです。変更されたIJG + NIFTY色空間コード私は今本当に立ち往生しています。だからどんな助けでも大歓迎です。

here に移動し、[ダウンロード]をクリックすると、元の画像(〜3 MB)を取得できます。下の左の画像は私が元の画像から得たものを示し、右はそれがどのように見えるべきかを示しています。

wrong colorscorrect colors (after removing metadata)

32
Ridcully

少なくとも結果の画像が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画像を作成すると、(メタデータを渡しても)間違った色が表示されますが、それでも問題はありません。

9
Ridcully

同様の問題がありました。返されるBufferedImageは、透明なピクセルがある場合に基づいたレンディションであり、ほとんどのpng/gifタイプのファイルに当てはまります。ただし、jpegに変換する場合は、このフラグをfalseに設定する必要があります。変換が適切に処理されるメソッドを作成する必要があるかもしれません。つまり:

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

そうでなければ、その「マルニッシュ」の倍音が保存された結果になります。 :)


4
wbudic

私も同様の問題を抱えていました。

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

の代わりに

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

私はこの問題に遭遇していましたが、実際にこれを処理するサードパーティのライブラリを見つけました。 https://github.com/haraldk/TwelveMonkeys

文字通り、私がしなければならなかったのは、これを私のMaven依存関係に含めることだけで、奇妙な色で出てきたjpegが正常に読み込まれ始めました。コードの行を変更する必要すらありませんでした。

3
James Fiala

これが「悪い」画像を良い画像に変換するアルゴリズムですが、画像が正しくレンダリングされないかどうかを自動的に検出する方法が見つからないため、それでも役に立ちません。

画像が(眼球以外で)悪くなるかどうかを検出する方法を誰かが見つけた場合は、お知らせください。 (たとえば、このいわゆる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());
    }
2
Mark Jeronimus

ここでは問題ないようです:

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));
    }
}
0
Andrew Thompson

画像をバイト配列からBase64に変換しようとしたときに、同様の問題が発生しました。問題の原因はアルファチャンネルの画像のようです。アルファチャネルを使用して画像を保存すると、アルファチャネルも保存され、画像の読み取りに使用される一部の外部プログラムは、4つのチャネルをCMYKとして解釈します。

BufferedImageのアルファチャネルを削除することにより、簡単な回避策を見つけました。これはばかげているかもしれませんが、それは確かに私にとってはうまくいきました。

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