7 Replies Latest reply on Jun 20, 2009 6:34 AM by 843802

    Testing if JPG uses CMYK ColorSpace or ColorModel

    843802
      Stated simply: How do I determine if a JPG uses a CMYK ColorSpace or ColorModel?

      My users can upload images into a web application which are scaled into thumbnails. When those images use a CMYK ColorSpace (or ColorModel), IE6 shows them a broken image (box with red X). (Firefox 3 does display an image, but the colors are wrong.) Once I know that the image is CMYK, I should be able to convert it to RGB so it displays properly...right?

      I've noticed that many other people have asked the same question, but there is no definitive answer. I believe that this article is pointing toward the solution, but I don't know enough to apply it. ([http://www.jroller.com/greenhorn/entry/adobe_photoshop_and_jpeg_cmyk])

      When I load the CMYK image with JAI.create("fileload", filename), the RenderedOp has a colorSpaceType
      of 5, which, according to java.awt.color.ColorSpace, is TYPE_RGB. The ColorSpace class has a TYPE_CMYK (=9), so why doesn't my image use that value?

      It also appears to be using the ICC Profile: ICC_ProfileRGB, which to me also seems wrong.

      I did notice that the ColorModel has a nBits array that has 4 elements when the image is CMYK and only 3 when it's RGB, however, I think that array is protected and I'm not convinced that would be a safe and correct test.

      If you can't tell by now, I am not expert on images or ColorSpaces, I'm just trying to get these images to show up properly within my web app using <img> tags.

      Thank you,
      Randy Stegbauer
        • 1. Re: Testing if JPG uses CMYK ColorSpace or ColorModel
          843802
          Some observations

          1) Using the default JPEGImageReader in java an exception is thrown complaining that it can't read CMYK images.

          2) Using the CLibJPEGImageReader in JAI-ImageIO, the image is correctly read into a BufferedImage that uses a CMYK color space. The resulting BufferedImage is naturaly a BufferedImage.TYPE_CUSTOM.

          3) Using the older jpeg codecs in JAI - what the "fileload" operation uses - the image is read, but incorrectly interpreted as an RGB image with an alpha channel (what you observed).

          So my recommendation is to drop JAI.create("fileload", filename), install [JAI-ImageIO|https://jai-imageio.dev.java.net/binary-builds.html], read the file via
          BufferedImage img = ImageIO.read(/*file, url, or input stream*/);
          and then color convert the resulting image into an RGB image for displaying purposes.
          int colorSpace = img.getColorModel().getColorSpace().getType();
          if(colorSpace == ColorSpace.TYPE_CMYK) {
              BufferedImage dst = new BufferedImage(img.getWidth(),
                      img.getHeight(),BufferedImage.TYPE_3BYTE_BGR);
              ColorConvertOp op = new ColorConvertOp(null);
              op.filter(img, dst);
              img = dst;
          }
          Technically, Java2D has the capability to display the CMYK BufferedImage, but because it's a TYPE_CUSTOM image you will find that it takes an unbearably long time to display (on the order of seconds or even minutes for a single graphics#drawImage command). Ironically, though, when the CMYK image is drawn directly the colors display correctly (when the image eventually gets drawn). On the other hand, the ColorConverOp 'brightens' the colors for lack of a better word. I think it might be a bug in the color conversion class or some sort of gamma conversion mishap.
          • 2. Re: Testing if JPG uses CMYK ColorSpace or ColorModel
            843802
            Thank you for the information.

            I will try this out and let everyone know the results.

            Randy Stegbauer
            • 3. Re: Testing if JPG uses CMYK ColorSpace or ColorModel
              843802
              I tried your code, but it still didn't work at identifying CMYK files until I also installed and began using the native DLL libraries. No one has ever mentioned that as a requirement. I always thought that the native libraries would improve performance but was not required for any functionality. I was wrong.

              So, as an added bonus here is some code to convert a CMYK image into an RGB image of any supported format.

              The only other thing that someone else mentioned, that I also noticed is that the RGB images are sometimes much lighter than the CMYK images. If anyone knows how to solve that problem, I would also be appreciative.

              Thank you,
              Randy Stegbauer

              package cmyk;
              
              import java.awt.color.ColorSpace;
              import java.awt.image.BufferedImage;
              import java.awt.image.ColorConvertOp;
              import java.io.File;
              import java.io.IOException;
              
              import javax.imageio.ImageIO;
              
              import org.apache.commons.lang.StringUtils;
              
              public class Main
              {
              
                  /**
                   * Creates new RGB images from all the CMYK images passed
                   * in on the command line.
                   * The new filename generated is, for example "GIF_original_filename.gif".
                   *
                   */
                  public static void main(String[] args)
                  {
                      for (int ii = 0; ii < args.length; ii++)
                      {
                          String filename = args[ii];
                          boolean cmyk = isCMYK(filename);
                          System.out.println(cmyk + ": " + filename);
                          if (cmyk)
                          {
                              try
                              {
                                  String rgbFile = cmyk2rgb(filename);
                                  System.out.println(isCMYK(rgbFile) + ": " + rgbFile);
                              }
                              catch (IOException e)
                              {
                                  System.out.println(e.getMessage());
                              }
                          }
                      }
                  }
              
                  /**
                   * If 'filename' is a CMYK file, then convert the image into RGB,
                   * store it into a JPEG file, and return the new filename.
                   *
                   * @param filename
                   */
                  private static String cmyk2rgb(String filename) throws IOException
                  {
                      // Change this format into any ImageIO supported format.
                      String format = "gif";
                      File imageFile = new File(filename);
                      String rgbFilename = filename;
                      BufferedImage image = ImageIO.read(imageFile);
                      if (image != null)
                      {
                          int colorSpaceType = image.getColorModel().getColorSpace().getType();
                          if (colorSpaceType == ColorSpace.TYPE_CMYK)
                          {
                              BufferedImage rgbImage =
                                  new BufferedImage(
                                      image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
                              ColorConvertOp op = new ColorConvertOp(null);
                              op.filter(image, rgbImage);
              
                              rgbFilename = changeExtension(imageFile.getName(), format);
                              rgbFilename = new File(imageFile.getParent(), format + "_" + rgbFilename).getPath();
                              ImageIO.write(rgbImage, format, new File(rgbFilename));
                          }
                      }
                      return rgbFilename;
                  }
              
                  /**
                   * Change the extension of 'filename' to 'newExtension'.
                   *
                   * @param filename
                   * @param newExtension
                   * @return filename with new extension
                   */
                  private static String changeExtension(String filename, String newExtension)
                  {
                      String result = filename;
                      if (filename != null && newExtension != null && newExtension.length() != 0);
                      {
                          int dot = filename.lastIndexOf('.');
                          if (dot != -1)
                          {
                              result = filename.substring(0, dot) + '.' + newExtension;
                          }
                      }
                      return result;
                  }
              
                  private static boolean isCMYK(String filename)
                  {
                      boolean result = false;
                      BufferedImage img = null;
                      try
                      {
                          img = ImageIO.read(new File(filename));
                      }
                      catch (IOException e)
                      {
                          System.out.println(e.getMessage() + ": " + filename);
                      }
                      if (img != null)
                      {
                          int colorSpaceType = img.getColorModel().getColorSpace().getType();
                          result = colorSpaceType == ColorSpace.TYPE_CMYK;
                      }
              
                      return result;
                  }
              }
              Edited by: rjsteg on May 11, 2009 7:56 AM
              Removed a couple of unnecessary methods.
              • 4. Re: Testing if JPG uses CMYK ColorSpace or ColorModel
                843802
                I tried your code, but it still didn't work at identifying CMYK files until I also installed and began using the native DLL libraries.
                Right, I mentioned this.
                2) Using the CLibJPEGImageReader in JAI-ImageIO, the image is correctly read into a BufferedImage that uses a CMYK color space. The resulting BufferedImage is naturaly a BufferedImage.TYPE_CUSTOM.
                The CLibJPEGImageReader is the natively accelerated jpeg image reader that comes with installing JAI-ImageIO. Sorry I wasn't clear on that. When you install JAI-ImageIO it automatically overrides the one already present as the prefered jpeg image reader. So any existing code that uses ImageIO.read(...) will automitically start using it. The core java JPEGImageReader is still available, but as a second choice. There are some things that the CLibJPEGImageReader fail at that the JPEGImageReader succeeds at, and vise versa. Neither are perfect.
                The only other thing that someone else mentioned, that I also noticed is that the RGB images are sometimes much lighter than the CMYK images.
                You should consider filing a bug report. For the test case, you can draw the CMYK BufferedImage onto an RGB one and show that while very very slow, the colors are perfect. Then you show the same CMYK BufferedImage passed through a ColorConvertOp. While fast, the colors are lighter than they should be.
                • 5. Re: Testing if JPG uses CMYK ColorSpace or ColorModel
                  843802
                  I hope not to be "poisoning" this thread with my question. Is it preferred to use the Image IO library over the JAI for the majority of cases? Thank you.
                  • 6. Re: Testing if JPG uses CMYK ColorSpace or ColorModel
                    843802
                    Hi everyone,
                    I have tried to use JAI ImageIO to open jpeg images as CMYK (unlike RGB), because by using only ImageIO capabilities it was not possible (is it an open bug?). Well, in my tests under windows jai worked aparently fine: I installed its version for jdk that puts 2 libs on classpath and 3 dll´s on path, and then the call ImageIO.read now reads such kind of file.
                    I have to problems related to this solution:
                    -> Problem 1 : Sometimes, and specially when I call ImageIO.read for those kind of images in a web project, I got the error message: "javax.imageio.IIOException: Unsupported Image Type". I am not sure if it is because we have to put those libs in a specific folder inside Tomcat (or another web container we use)....... That call is only working here in standalone applications. Why?
                    -> Problem 2: I couldn´t use JAI ImageIO on linux 64bits. The execution tells the same error as we get when JAI ImageIO is not installed. I put the .so inside $JDK_FOLDER$/jre/bin and the .jar's inside $JDK_FOLDER$/jre/lib/ext but it didn´t work... Also, the .bin installer for linux 64bits is not working properly.
                    • 7. Re: Testing if JPG uses CMYK ColorSpace or ColorModel
                      843802
                      IcaroDourado wrote:
                      Problem 1 : Sometimes, and specially when I call ImageIO.read for those kind of images in a web project, I got the error message: "javax.imageio.IIOException: Unsupported Image Type". I am not sure if it is because we have to put those libs in a specific folder inside Tomcat (or another web container we use)....... That call is only working here in standalone applications. Why?
                      The ImageIO.read(...) method picks the first ImageReader that claims it can read the given image format. You're probably trying to load CMYK jpeg images. The core JPEGImageReader in java will say that it can read a jpeg file, but when it actually tries to read the file an sees the CMYK stuff, then it will throw that exact exception. When you install JAI-ImageIO then CLibJPEGImageReader is the first one to claim that it can read the jpeg file, and the reading succeeds (because it can read CMYK images).
                      IcaroDourado wrote:
                      Problem 2: I couldn´t use JAI ImageIO on linux 64bits. The execution tells the same error as we get when JAI ImageIO is not installed. I put the .so inside $JDK_FOLDER$/jre/bin and the .jar's inside $JDK_FOLDER$/jre/lib/ext but it didn´t work... Also, the .bin installer for linux 64bits is not working properly.
                      The label of the installer is "jai_imageio-1_1-lib-linux-amd64-jdk.bin." So maybe you have to have an amd cpu?
                      negora wrote:
                      I hope not to be "poisoning" this thread with my question. Is it preferred to use the Image IO library over the JAI for the majority of cases? Thank you.
                      ImageIO is arguably a more fully featured library than JAI for reading and writing images. It's a library dedicated soly for that purpose, while JAI is more about the image processing once the image is loaded. But if you don't actually want to do anything fancy - you just want to load the image for some image processing - then JAI should be perfectly fine. And indeed, loading the image with JAI will give you a ready made PlanarImage instead of having to wrap a BufferedImage. JAI also defers loading the image until it is actually needed, and it offers some tiling capabilities that ImageIO doesn't.

                      Edit

                      Oh yea, one last thing.
                      rjsteg
                      The only other thing that someone else mentioned, that I also noticed is that the RGB images are sometimes much lighter than the CMYK images. If anyone knows how to solve that problem, I would also be appreciative.
                      After tinkering with some stuff, I've found a solution to this. The solution programs to the implementation of the ColorConvertOp class and not the API, so I don't know about it's portability between java versions or OS's. But here it is (this works on vista and java 1.6)
                      public static BufferedImage sophisticatedColorConvert(BufferedImage src,
                                                                             BufferedImage dst) {
                          ICC_ColorSpace srcCS =
                                      (ICC_ColorSpace) src.getColorModel().getColorSpace();
                       
                          ICC_Profile srcProf = srcCS.getProfile();
                          byte[] header = srcProf.getData(ICC_Profile.icSigHead);
                          intToBigEndian(ICC_Profile.icSigInputClass,header,12);
                          srcProf.setData(ICC_Profile.icSigHead,header);
                       
                          ColorConvertOp op = new ColorConvertOp(null);
                          return op.filter(src, dst);
                      }
                      private static void intToBigEndian(int value, byte[] array, int index) {
                          array[index]   = (byte) (value >> 24);
                          array[index+1] = (byte) (value >> 16);
                          array[index+2] = (byte) (value >>  8);
                          array[index+3] = (byte) (value);
                      }
                      It should prevent the brightness going from any non-sRGB color space to sRGB.