Search code examples
javaimagebufferedimagecolor-spacecolor-profile

In Java converting an image to sRGB makes the image too bright


I have multiple images with a custom profile embedded in them and want to convert the image to sRGB in order to serve it up to a browser. I have seen code like the following:

BufferedImage image = ImageIO.read(fileIn);
ColorSpace ics = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorConvertOp cco = new ColorConvertOp(ics, null);
BufferedImage result = cco.filter(image, null);
ImageIO.write(result, "PNG", fileOut);

where fileIn and fileOut are File objects representing the input file and output file respectively. This works to an extent. The problem is that the resulting image is lighter than the original. If I was to convert the color space in photoshop the colors would appear the same. In fact if I pull up both images with photoshop and take a screen shot and sample the colors, they are the same. What is photoshop doing that the code above isn't and what can I do to correct the problem?

There are various types of images being converted, including JPEG, PNG, and TIFF. I have tried using TwelveMonkeys to read in JPEG and TIFF images and I still get the same effect, where the image is too light. The conversion process seems worst when applied to an image that didn't have an embedded profile in the first place.

Edit:

enter image description here enter image description here enter image description here

I've added some sample images to help explain the problem.

  1. This image is the one with the color profile embedded in it. Viewed on some browsers there won't be a noticeable difference between this one and the next but viewed in Chrome on Mac OSX and Windows it currently appears darker than it should. This is where my problem originates in the first place. I need to convert the image to something that will show up correctly in Chrome.
  2. This is an image converted with ImageMagick to the Adobe RGB 1998 color profile, which Chrome appears to be able to display correctly.
  3. This is the image that I converted using the code above and it appears lighter than it should.

(Note that the images above are on imgur so to make them larger, simply remove the "t" from the end of the filename, before the file extension.)


Solution

  • I found a solution that doesn't require ImageMagick. Basically Java doesn't respect the profile when loading the image so if there is one it needs to get loaded. Here is a code snippet of what I did to accomplish this:

    private BufferedImage loadBufferedImage(InputStream inputStream) throws IOException, BadElementException {
        byte[] imageBytes = IOUtils.toByteArray(inputStream);
        BufferedImage incorrectImage = ImageIO.read(new ByteArrayInputStream(imageBytes));
    
        if (incorrectImage.getColorModel() instanceof ComponentColorModel) {
    
            // Java does not respect the color profile embedded in a component based image, so if there is a color
            // profile, detected using iText, then create a buffered image with the correct profile.
            Image iTextImage = Image.getInstance(imageBytes);
            com.itextpdf.text.pdf.ICC_Profile iTextProfile = iTextImage.getICCProfile();
    
            if (iTextProfile == null) {
                // If no profile is present than the image should be processed as is.
                return incorrectImage;
            } else {
                // If there is a profile present then create a buffered image with the profile embedded.
                byte[] profileData = iTextProfile.getData();
                ICC_Profile profile = ICC_Profile.getInstance(profileData);
                ICC_ColorSpace ics = new ICC_ColorSpace(profile);
    
                boolean hasAlpha = incorrectImage.getColorModel().hasAlpha();
                boolean isAlphaPremultiplied = incorrectImage.isAlphaPremultiplied();
                int transparency = incorrectImage.getTransparency();
                int transferType = DataBuffer.TYPE_BYTE;
                ComponentColorModel ccm = new ComponentColorModel(ics, hasAlpha, isAlphaPremultiplied, transparency, transferType);
                return new BufferedImage(ccm, incorrectImage.copyData(null), isAlphaPremultiplied, null);
            }
        }
        else if (incorrectImage.getColorModel() instanceof IndexColorModel) {
            return incorrectImage;
        }
        else {
            throw new UnsupportedEncodingException("Unsupported color model type.");
        }
    }
    

    This answer does use iText, which is generally used for PDF creation and manipulation, but it happens to process the ICC profiles correctly and I'm already depending on it for my project so it happens to be a much better choice than ImageMagick.

    The code in the question then ends up as follows:

    BufferedImage image = loadBufferedImage(new FileInputStream(fileIn));
    ColorSpace ics = ColorSpace.getInstance(ColorSpace.CS_sRGB);
    ColorConvertOp cco = new ColorConvertOp(ics, null);
    BufferedImage result = cco.filter(image, null);
    ImageIO.write(result, "PNG", fileOut);
    

    which works great.