bufferedimagejavax.imageiocolor-space

How can I change the color profile of a BufferedImage without changing the pixel values?


I have a BufferedImage with an invalid color profile. I want to replace the color profile with an ICC_Profile without recomputing the pixel values in the image. How can I do that?

In particular the com.sun.imageio.plugins.png.PNGImageReader reads an image with a DCI-P3 ICC profile and produces a BufferedImage with a CS_sRGB ColorSpace but the pixel values correspond to the original DCI-P3 color space and the resulting image has the wrong colors.

If I could just swap the sRGB ColorSpace with a DCI-P3 ColorSpace from the ICC profile the colors would be correct. If I run a ColorConvertOp with a destination ICC_Profile on the image then the resulting colors are (differently) wrong because it interprets the source pixels using the wrong sRGB ColorSpace.


Solution

  • I haven't actually tried this, as there's no code in the question to verify against, but I believe this should work:

    If you have the DCI-P3 profile available, you could just create a Java ColorSpace from that, then create a ColorModel using this color space. The new color model needs to have the same type and the same number of components as that in the original image. Finally, create a new BufferedImage using this color model and the Raster of the original image. Something like this:

    BufferedImage original; // with incorrect color profile
    
    ColorSpace dciP3 = new ICC_ColorSpace(ICC_Profile.getInstance(dciP3ProfilePath));
    ColorModel cm = new ComponentColorModel(dciP3, new int[] {8, 8, 8}, 
                                            false, false, Transparency.OPAQUE,
                                            DataBuffer.TYPE_BYTE);
    
    BufferedImage corrected = new BufferedImage(cm, original.getRaster(), cm.isAlphaPremultiplied(), null);
    

    The above is assuming that your image is RGB (no alpha/transparency), 8 bits/sample and that the original image has a ComponentColorModel. If this is incorrect, you need to modify the color model accordingly. Some common examples:

    ComponentColorModel with alpha, 16 bits/sample:

    new ComponentColorModel(dciP3, new int[] {16, 16, 16, 16}, 
                            true, false, Transparency.TRANSLUCENT,
                            DataBuffer.TYPE_USHORT);
    

    DirectColorModel with alpha, 8 bits/sample, ARGB order:

    new DirectColorModel(dciP3, 32, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000,
                         false, DataBuffer.TYPE_INT)
    

    As there is no duplication or conversion of the pixel data in this case, this operation should be really fast. But it will produce a TYPE_CUSTOM BufferedImage due to the non-standard ICC profile. It will look correct, but might be slow to paint on screen. If you need an image that is fast to paint, using ColorConvertOp might be a better option.

    It should be possible to do an in-place conversion on the raster, directly from the DCI-P3 profile to sRGB like this:

    BufferedImage original; // with incorrect sRGB profile
    
    ICC_Profile dciP3 = ICC_Profile.getInstance(dciP3ProfilePath);
    ICC_Profile sRGB = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
    ColorConvertOp convert = new ColorConvertOp(new ICC_Profile[] {dciP3, sRGB}, null);
    
    WritableRaster raster = original.getRaster();
    if (raster.getNumNamds() > 3) { // or > profile.getNumComponents() 
        // Create raster that filters out the color components and leaves alpha untouched
        raster = raster.createWritableChild(0, 0, raster.getWidth(), raster.getHeight(), 
                                            0, 0, new int[] {0, 1, 2});
    }
    
    convert.filter(raster, raster); 
    
    // original now have corrected pixels in sRGB profile