BufferedImage,转换为字节数组后返回的相同图像,但灰度转换后不同

Posted

技术标签:

【中文标题】BufferedImage,转换为字节数组后返回的相同图像,但灰度转换后不同【英文标题】:BufferedImage, same image after conversion to byte array and back, but different after grey conversion 【发布时间】:2014-02-17 03:01:04 【问题描述】:

我需要将BufferedImages(RGB,无伪影)转换为字节数组以进行发送。在进行单元测试时,我偶然发现了BufferedImage、我的期望或我的代码的问题。

BufferedImage 转换为字节数组并在再次转换后返回相同的字节数组。我假设它是相同的图像。但是,如果我将原始图像和看似相同的反向转换图像都转换为灰度,这会给我完全不同的图像(不仅仅是一个字节,真的不同!)。显然,我希望灰度图像与源相同。有人可以启发我吗?我期待有什么不对吗?

我有一个完整的精简示例。

import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorConvertOp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import javax.imageio.ImageIO;    

public class GreyImageProblem 

    private static Random random = new Random();
    private static int randInt(int from, int to) 
        return random.nextInt(to-from+1)+from;
    

    private static java.awt.Color randColor() 
        return new java.awt.Color(randInt(0,256*256*256));
    

    // a random image with different RGB colors
    public static BufferedImage genImage() 
        int width = randInt(180, 640);
        int height = randInt(120, 480);
        BufferedImage im = new BufferedImage(width, height, 
            BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = im.createGraphics();      
        graphics.setStroke(new BasicStroke(6));
        Rectangle rectangle = new Rectangle(0, 0, width, height);
        for (int i=0; i < 42; i+=1) 
            int x = randInt(0, width), y = randInt(0, height);
            int sw = randInt(0, width)/10, sh = randInt(0, height)/10;
            graphics.setColor(randColor());
            rectangle.setBounds(x, y, sw, sh);
            graphics.draw(rectangle);               
        
        return im;
    

    // make it grey
    public static BufferedImage toGrey(BufferedImage im) 
        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); 
        BufferedImageOp op = new ColorConvertOp(cs, null); 
        return op.filter(im, null);
    

    // make it byte array
    public static byte[] toByteArray(BufferedImage im) 
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try 
            ImageIO.write(im, "png", baos);
            baos.flush();
            byte[] ret = baos.toByteArray();
            baos.close();
            return ret;
         catch (IOException e) 
            throw new RuntimeException(e);
               
    

    // make it an image again
    public static BufferedImage fromByteArray(byte[] ba) 
        InputStream in = new ByteArrayInputStream(ba);
        try 
            BufferedImage im = ImageIO.read(in);
            return im;
         catch (IOException e) 
            throw new RuntimeException(e);
        
    

    public static boolean sameByteArray(byte[] a, byte[] b) 
        if (a.length != b.length) 
            System.out.format("sameByteArray: a.length=%d, b.length=%d\n", 
                       a.length, b.length);
            return false;
        
        for (int i=0; i < a.length; i++) 
            if (a[i] != b[i]) 
                return false;
            
        
        return true;
    

    public static void main(String[] args) 
        BufferedImage image = genImage();
        byte[] imagebytes = toByteArray(image);
        BufferedImage imageAgain = fromByteArray(imagebytes);
        BufferedImage imageGrey = toGrey(image);
        BufferedImage imageAgainGrey = toGrey(imageAgain);
        if (sameByteArray(imagebytes, toByteArray(imageAgain))) 
            System.out.println("Both are the same images");
        
        if (!sameByteArray(toByteArray(imageGrey), toByteArray(imageAgainGrey))) 
            System.out.println("AAAAAAaaaaaaaaaaahhhhhhhhhhhh");
            System.out.println("Why they have different grey images!!!!");
        
    

感谢您的帮助。

【问题讨论】:

对不起,对我有用,我没有看到第二个 if 语句的输出...您可以尝试将所有 4 个图像写入磁盘并查看是否有任何差异 好吧,我刚刚在几台不同的电脑上试了试,结果——让我大吃一惊。它似乎适用于 1.6.0_27、64 位。但它肯定会在几台 1.7.0_25 的计算机上失败。至少,它似乎是一个 Java Bug,而不是别的什么 (?) 也许尝试使用CS_LINEAR_GRAY 进行转换?可能会有所作为。 更新:这是一个错误,已经提交了错误报告,还没有答案。 1.7.0_55、IcedTea 2.4.7、24.51-b03 仍然存在问题。但是,它似乎与 1.8.0_05-b13、25.5-b02 一起工作得很好。 一年后,在 java 1.7.0_79 (IcedTea 2.5.5,7u79-2.5.5-1~deb7u1) 上仍然有问题,但在 Oracle java 1.8.0_45 上运行良好 【参考方案1】:

小心,BufferedImage.TYPE_INT_RGB 将 (R,G,B) 三元组编码为 int 值。当您想将使用 int 值编码的 DataBuffer 转换为字节时,您可以这样做:

BufferedImage imint = new BufferedImage(N, N, BufferedImage.TYPE_INT_RGB) ;
BufferedImage imbyte = new BufferedImage(imint.getWidth(), imint.getHeight(), BufferedImage.TYPE_3BYTE_BGR) ;
int[] databufferint = ((DataBufferInt)imint.getRaster().getDataBuffer()).getData() ;
byte[] databufferbyte = ((DataBufferByte)imbyte.getRaster().getDataBuffer()).getData() ;
for (int i=0 ; i < databufferint.length ; i++)
    
    int R = (databufferint[i] & 0xFF0000) >> 16 ;
    int G = (databufferint[i] & 0x00FF00) >> 8 ;
    int B = databufferint[i] & 0x0000FF ;
    databufferbyte[i*3] = (byte)B ;
    databufferbyte[i*3+1] = (byte)G ;
    databufferbyte[i*3+2] = (byte)R ;
    

因此,您将获得 3 字节(24 位)编码,而不是 int(32 位)编码,这样可以节省 25% 的内存(未使用的 alpha 通道)。但这仍然是一种颜色编码/表示。如果从RGB转灰度,可以使用CIE 601或者CIE 709转换,但是没有回来(操作不是双射)。

【讨论】:

以上是关于BufferedImage,转换为字节数组后返回的相同图像,但灰度转换后不同的主要内容,如果未能解决你的问题,请参考以下文章

从字节数组转换为 base64 并返回

将字节数组转换为字符串并返回字节数组的问题

将 char 数组转换为字节数组并再次返回

如何在 Java 中将 libGDX 的纹理转换为字节数组并再次返回

Xamarin 安卓。将字节数组转换为位图。 Skia 解码器返回 false

将范围为 -128 到 127 的字节数组转换为字符串数组