6

I need to convert BufferedImages (RGB, no artefacts) to byte arrays for sending. While unit testing I stumbled over a problem with either BufferedImage, my expectation, or my code.

A BufferedImage converted to a byte array and back yields after conversion again the same byte array. I assume it is the same image. However, if I convert both the original and the seemingly identical back-converted image to grey scale this gives me totally different images (not just a byte, really different!). Obviously, I expect the grey scale images to be identical as the source is identical. Can anybody enlighten me? Am I expecting something wrong?

I have a complete stripped down example.

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!!!!");
        }
    }
}

Thanks for any help.

Anindya Dutta
  • 1,972
  • 2
  • 18
  • 33
pba
  • 524
  • 3
  • 13
  • 2
    Sorry, works for me, I don't see the output from the second if statement...You could try writing all 4 images to disk and seeing if there are any differences – MadProgrammer Jan 26 '14 at 08:15
  • Ok, I just tried on a couple of different computers and - surprise. It seems to work ok with 1.6.0_27, 64 bit. But it definitely fails on several computers with 1.7.0_25. At least, it seems to be a Java Bug and not something else (?) – pba Jan 26 '14 at 15:43
  • Maybe try using `CS_LINEAR_GRAY` for the conversion? Might make a difference. – Harald K Jan 26 '14 at 20:28
  • I only have CS_GRAY, there is not CS_LINEAR_GRAY. I do have CS_LINEAR_RGB, which works but which is not what is intended. – pba Jan 27 '14 at 00:34
  • 1
    update: It is a bug, a bug report has been filed, no answer yet. It is still buggy with 1.7.0_55, IcedTea 2.4.7, 24.51-b03. However, it seems to work just fine with 1.8.0_05-b13, 25.5-b02. – pba Jul 01 '14 at 20:04
  • 1
    after a year, still buggy on java 1.7.0_79 (IcedTea 2.5.5,7u79-2.5.5-1~deb7u1) but works fine on Oracle java 1.8.0_45 – pba Jun 18 '15 at 16:18

1 Answers1

0

Be careful, the BufferedImage.TYPE_INT_RGB encodes the (R,G,B) triplet into a int value. When you want to convert a DataBuffer encoded with int values to byte, you can do:

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

So instead of a int (32 bits) encoding, you will get a 3 byte (24 bits) encoding, which gives you 25% memory saving (the unused alpha canal). But that's still a color encoding/representation. If you go from RGB to gray scale, you can to use the CIE 601 or CIE 709 conversions, but then there is no coming back (the operation is not a bijection).

FiReTiTi
  • 5,597
  • 12
  • 30
  • 58