Search code examples
javabufferedimagecolor-space

How can I set pixels in Java BufferedImages using non-ARGB color spaces?


I'm writing an application that needs to work with 16-bit "5-5-5" RGB colors (that is, 5 bits for each color and one bit of padding). In order to handle these images, I am using the BufferedImage class provided by AWT. The BufferedImage class specifically allows for the usage of non-RGB color spaces by taking either a ColorModel object or a predefined image type constant - one of which is the 5-5-5 pixel format that I need.

My problem is this: the BufferedImage "setRGB()" method states in its description that color values provided are "assumed to be in the default RGB color model, TYPE_INT_ARGB, and default sRGB color space" (per the BufferedImage documentation page). No other method seems to accept values designed for different color spaces, either.

Is there a way to use my non-standard color space directly with BufferedImage, or would I have to rely on the class's internal color conversion mechanisms to handle all of my colors? (Or am I just misreading/misunderstanding something about how the class works?)


Solution

  • BufferedImage.TYPE_USHORT_555_RGB still uses a completely standard RGB color space (in fact, it uses sRGB), so I don't think a different color space is what you are looking for.

    If you want to perform painting or other operations in Java, just use the normal methods like setRGB/getRGB() and createGraphics()/Grapics2D. Everything will be properly converted to and from the packed USHORT_555_RGB format for you.

    For example:

    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_USHORT_555_RGB);
    
    // Do some custom painting
    Graphics2D g = image.createGraphics();
    g.drawImage(otherImage, 0, 0, null); // image type here does not matter
    g.setColor(Color.ORANGE);            // Color in sRGB, but does not matter
    g.fillOval(0, 0, w, h);
    g.dispose();
    
    image.setRGB(0, h/2, w, 1, new int[w]); // Silly way to create a horizontal black line at the center of the image... Don't do this, use fillRect(0, h/2, 1, w)! ;-)  
    
    // image will still be USHORT_555_RGB *internally*
    

    However, if you have pixel data in the USHORT_555_RGB format (ie. from an external library/api/service), it may be faster and more accurate to set these values directly to the raster/databuffer. Or if you need to pass the pixel values back to the same library/api/service.

    For example, using the Raster:

    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_USHORT_555_RGB);
    
    // Some fictional API. It's assumed that data.length == w * h
    short[] apiPixels = api.getPixelsUSHORT_555_RGB(w, h);
    
    WritableRaster raster = image.getRaster();
    
    // Set short values to image
    raster.setDataElements(0, 0, w, h, apiPixels);
            
    // Get short values from image
    short[] pixels = (short[]) raster.getDataElements(0, 0, w, h, null); // TYPE_USHORT_555_RGB -> always short[]
    api.setPixels(pixels, w, h); // Another fictional API
    

    Or, alternatively, use the DataBuffer:

    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_USHORT_555_RGB);
    
    // Some fictional API. It's assumed that data.length == w * h
    short[] apiPixels = api.getPixelsUSHORT_555_RGB(w, h);
    
    DataBufferUShort buffer = (DataBufferUShort) image.getRaster().getDataBuffer(); // TYPE_USHORT_555_RGB -> always DataBufferUShort
    
    // Set short values to image
    System.arraycopy(apiPixels, 0, buffer.getData(), 0, apiPixels.length);
    
    // Get short values from image
    api.setPixels(buffer.getData(), w, h);
    

    In most cases it does not matter which method you use, but the first approach (using Raster only) may keep the image managed, which will make images display faster on screen from your Java process.


    PS: If a different color space is really what you need (ie. the pixel array from the external library/api/service uses a different color space, and you need to view the pixels in this color space), you can create a BufferedImage in USHORT_555_RGB style with a custom color space like this:

    // Either use one of the built-in color spaces, or load one from disk
    ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
    ColorSpace colorSpaceToo = new ICC_ColorSpace(ICC_Profile.getInstance(Files.newInputStream(new File("/path/to/custom_rgb_profile.icc").toPath())));
    
    // Create a color model using your color space, TYPE_USHORT and 5/5/5 mask, no transparency
    ColorModel colorModel = new DirectColorModel(colorSpace, 15, 0x7C00, 0x03E0, 0x001F, 0, false, DataBuffer.TYPE_USHORT);
    
    // And finally, create an image from the color model and a compatible raster
    BufferedImage imageToo = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(w, h), colorModel.isAlphaPremultiplied(), null);
    

    Just remember that as the Java2D graphics operations and setRGB/getRGB are still using sRGB, now all operations on your image will be converted back and forth between your color space and sRGB. Performance will not be as good.