Вопрос

I'm trying to send a BufferedImage over socket, I do this by converting the image to byte[] and then send it over after encoding it in Base64. I'm sending over 2 BufferedImages, one of them is "full", the other one is about 50% transparent. The problem I'm having, is that when they arrive, the second image is still visually transparent, but when I get the data array via Raster, it has been changed.

I made a small test code to demonstrate the problem;

        BufferedImage levelBufferedOriginal = ...
        BufferedImage backgroundBufferedOriginal = ...
        
        byte[] levelDataOriginal = ((DataBufferByte) levelBufferedOriginal.getRaster().getDataBuffer()).getData();
        byte[] backgroundDataOriginal = ((DataBufferByte) backgroundBufferedOriginal.getRaster().getDataBuffer()).getData();
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] temp = null, temp2=null;
        try {
            ImageIO.write(levelBufferedOriginal, "png", baos);
            baos.flush();
            temp = baos.toByteArray();
            baos.close();
            
            baos=new ByteArrayOutputStream();
            ImageIO.write(backgroundBufferedOriginal, "png", baos);
            baos.flush();
            temp2 = baos.toByteArray();
            baos.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        
        
        
        BufferedImage levelBufferedNew = null;
        BufferedImage backgroundBufferedNew = null;
        
        try {
            levelBufferedNew = ImageIO.read(new ByteArrayInputStream(temp));
            backgroundBufferedNew = ImageIO.read(new ByteArrayInputStream(temp2));
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        byte[] levelDataNew = ((DataBufferByte) levelBufferedNew.getRaster().getDataBuffer()).getData();
        byte[] backgroundDataNew = ((DataBufferByte) backgroundBufferedNew.getRaster().getDataBuffer()).getData();
        
        
        System.out.println("LEVEL: " + Arrays.equals(levelDataOriginal, levelDataNew));
        System.out.println("BACKGROUND: " + Arrays.equals(backgroundDataOriginal, backgroundDataNew));

All I do here, is simply transform the BufferedImage to byte[], then back, and compare the data I get from DataBufferByte. The output is

LEVEL: false

BACKGROUND: true

Background is the "full" image, and Level is the one with some transparent pixels.

If the general idea is wrong, I would like to hear another, all I want is to be able to exactly recreate 2 bufferedImages.

Это было полезно?

Решение

edit: What we have established so far:

  • The images (both before and after) are TYPE_BYTE_INDEXED (13) with IndexColorModel (color map)
  • The before image has a transparent color in the color map, at index 255 (which is the value -1 in the byte array, as Java uses signed bytes). The after image has a different value at this index, that is not transparent.
  • The images are serialized/deserialized in PNG format, using ImageIO
  • The images are visually equal, but the raw pixel data (the byte array) differs

Which leads to the conclusion that the ImageIO PNGImageWriter re-arranges the entries in the color map when writing, resulting in different pixel data/color map.

This basically leaves us with two options:

  1. Serialize the image data in a different way, to assure the color map/pixel data is not modified. It is possible to send the pixel data array, along with the color map array and the height/width of the image, and then re-create the image exactly at the client. This is quite a bit of code, and is probably covered by other questions on SO.

  2. Don't rely on the pixel data/color maps being the same. Use the value of ((IndexColorModel) levelBufferedNew.getColorModel()).getTransparentPixel() to test for/set transparency instead of the hardcoded value -1. This requires pretty much no other change in your code.

Note: These solutions will only work for TYPE_BYTE_INDEXED (13) images.

For a more generic (but possibly slower) approach, use the code in the original answer to set transparent parts, and use (levelBufferedNew.getRGB(x, y) >> 24) == 0 to test for transparency. This should work even for TYPE_INT_ARGB or TYPE_4BYTE_ABGR.

original answer:

Instead of fiddling with the image at byte array level, why not try using normal Java2D? ;-)

Something like:

Graphics2D g = levelBufferedNew.createGraphics();
try {
    g.setComposite(AlphaComposite.Clear);
    g.fillOval(x, y, w, h); // The area you want to make transparent
}
finally {
    g.dispose();
}

...should work.

PS: As the images use IndexColorModel, you can use the getTransparentPixel() to get the transparent pixel index, instead of relying on it being at a certain index (-1/255). Then you can still manipulate at byte array level. ;-)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top