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.
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