How to create a BufferedImage for 32 bits per sample, 3 sample image data

I am trying to create a BufferedImage from some image data which is a byte array. The image is in RGB format with 3 samples per pixel - R, G and B and 32 bits per sample (not all 3 samples for each sample).

Now I want to create a BufferedImage from this byte array. This is what I did:

        ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {32, 32, 32}, false, false, Transparency.OPAQUE, DataBuffer.TYPE_INT);
        Object tempArray = ArrayUtils.toNBits(bitsPerSample, pixels, samplesPerPixel*imageWidth, endian == IOUtils.BIG_ENDIAN);
        WritableRaster raster = cm.createCompatibleWritableRaster(imageWidth, imageHeight);
        raster.setDataElements(0, 0, imageWidth, imageHeight, tempArray); 
        BufferedImage bi = new BufferedImage(cm, raster, false, null);

      

The above code works with 24 bits per RGB image, but not 32 bits per sample. The generated image is the garbage shown to the right of the image. It should look like the left side of the image.

Note. The only image reader on my machine that can read this image is ImageMagick. All others show similar results as garbage to the right of the next image.

ArrayUtils.toNBits () just converts the byte array to an int array with the correct consistency. I am sure this is correct as I have tested Cross with other methods to create the same int array.

I think the problem may come from the fact that I am using all 32 bits of int to represent a color that will contain negative values. It looks like I need a long data type, but the DataBuffer type has not existed for a long time.

ComponentColorModel instances created with the transfer types DataBuffer.TYPE_BYTE, DataBuffer.TYPE_USHORT, and DataBuffer.TYPE_INT have pixel sample values โ€‹โ€‹that are treated as the unsigned integral of the value.

The above quote is from the Java doc for ComponentColorModel. This means that the 32-bit sample is treated as an unsigned integer. Then the problem may be somewhere else.

Does any body have a similar issue and got a workaround, or I may have done something wrong here?

Update2 . The "real" problem is that when 32-bit sampling is used, the algorithm for the ComponentColorModel will shift 1 to the left 0 times (1 <0) since the shift on int is always between 0 and 31, inclusive. This is not the expected value. To fix this problem (actually shifting left 32 times), there is only one thing to do, 1 to 1, as shown in the hotfix below.

Update : From HaraldK's answer and comments, we finally agreed that the issue is with the Java ComponentColorModel not handling the 32-bit sample correctly. HaraldK's suggested solution also works for my case. Below is my version:

import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;

public class Int32ComponentColorModel extends ComponentColorModel {
   //
   public Int32ComponentColorModel(ColorSpace cs, boolean alpha) {
        super(cs, alpha, false, alpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE, DataBuffer.TYPE_INT);
   }

   @Override
   public float[] getNormalizedComponents(Object pixel, float[] normComponents, int normOffset) {
       int numComponents = getNumComponents();

       if (normComponents == null || normComponents.length < numComponents + normOffset) {
           normComponents = new float[numComponents + normOffset];
       }

       switch (transferType) {
           case DataBuffer.TYPE_INT:
               int[] ipixel = (int[]) pixel;
               for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) {
                   normComponents[nc] = ipixel[c] / ((float) ((1L << getComponentSize(c)) - 1));
               }
               break;
           default: // I don't think we can ever come this far. Just in case!!!
               throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
       }

       return normComponents;
   }
}

      

enter image description here

+3


source to share


1 answer


Update:

This seems to be a known bug: ComponentColorModel.getNormalizedComponents () does not handle 32-bit TYPE_INT , reports 10 (TEN!) Years ago, against Java 5.

Java surface is now partially open. We can now suggest a patch and with some luck it will be appreciated for Java 9 or so ... :-P

The error suggests the following workaround:

Subclass ComponentColorModel and override getNormalizedComponents () to properly handle 32-bit sample TYPE_INT data by dividing the input pixel value by "Math.pow (2, 32) - 1" when working with that data instead of using the erroneous offset bit. (Using a floating point value is okay since getNormalizedComponents () will convert everything to floating point anyway).

My fix is โ€‹โ€‹slightly different, but the basic idea is the same (feel free to optimize as you see fit :-)):

private static class TypeIntComponentColorModel extends ComponentColorModel {
    public TypeIntComponentColorModel(final ColorSpace cs, final boolean alpha) {
        super(cs, alpha, false, alpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_INT);
    }

    @Override
    public float[] getNormalizedComponents(Object pixel, float[] normComponents, int normOffset) {
        int numComponents = getNumComponents();

        if (normComponents == null) {
            normComponents = new float[numComponents + normOffset];
        }

        switch (transferType) {
            case DataBuffer.TYPE_INT:
                int[] ipixel = (int[]) pixel;
                for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) {
                    normComponents[nc] = ((float) (ipixel[c] & 0xffffffffl)) / ((float) ((1l << getComponentSize(c)) - 1));
                }
                break;
            default:
                throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
        }

        return normComponents;
    }
}

      




Consider the below code. Running as it is, for me it displays a predominantly black image, and the top right quarter of white with a black circle. If I change the datatype to TYPE_USHORT

(uncomment the line transferType

) it displays half / half white and a linear gradient from black to white with an orange circle in the middle (as it should).

Usage ColorConvertOp

for conversion to standard type doesn't seem to matter.

public class Int32Image {
    public static void main(String[] args) {
        // Define dimensions and layout of the image
        int w = 300;
        int h = 200;
        int transferType = DataBuffer.TYPE_INT;
//        int transferType = DataBuffer.TYPE_USHORT;

        ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false, Transparency.OPAQUE, transferType);
        WritableRaster raster = colorModel.createCompatibleWritableRaster(w, h);
        BufferedImage image = new BufferedImage(colorModel, raster, false, null);

        // Start with linear gradient
        if (raster.getTransferType() == DataBuffer.TYPE_INT) {
            DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
            int[] data = buffer.getData();

            for (int y = 0; y < h; y++) {
                int value = (int) (y * 0xffffffffL / h);

                for (int x = 0; x < w; x++) {
                    int offset = y * w * 3 + x * 3;
                    data[offset] = value;
                    data[offset + 1] = value;
                    data[offset + 2] = value;
                }
            }
        }
        else if (raster.getTransferType() == DataBuffer.TYPE_USHORT) {
            DataBufferUShort buffer = (DataBufferUShort) raster.getDataBuffer();
            short[] data = buffer.getData();

            for (int y = 0; y < h; y++) {
                short value = (short) (y * 0xffffL / h);

                for (int x = 0; x < w; x++) {
                    int offset = y * w * 3 + x * 3;
                    data[offset] = value;
                    data[offset + 1] = value;
                    data[offset + 2] = value;
                }
            }
        }

        // Paint something (in  color)
        Graphics2D g = image.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, w / 2, h);
        g.setColor(Color.ORANGE);
        g.fillOval(100, 50, w - 200, h - 100);
        g.dispose();

        System.out.println("image = " + image);

//        image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));

        JFrame frame = new JFrame();
        frame.add(new JLabel(new ImageIcon(image)));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

      

It seems to me that this suggests ColorModel

that something is wrong using transferType TYPE_INT

. But I would be happy to be wrong .; -)

Another thing you might try is to scale the values โ€‹โ€‹to 16 bits, use the raster and color models, TYPE_USHORT

and see if that changes. I bet it will, but I'm too lazy to try .; -)

+2


source







All Articles