JDK-8057889 : Mismatch in constants between JPEGImageWriter and jpeglib.h causes exception
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.imageio
  • Affected Version: 7u67,8
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_7
  • CPU: x86
  • Submitted: 2014-09-08
  • Updated: 2014-09-24
  • Resolved: 2014-09-09
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
JDK 7 and JDK 8, all updates

ADDITIONAL OS VERSION INFORMATION :
Windows 7 32 and 64 bit, Linux, etc

A DESCRIPTION OF THE PROBLEM :
JPEGImageWriter fails writing CMYK / YCCK files because of a mismatch in constant definitions between Java code and native C code.

com.sun.imageio.plugins.jpeg.JPEG defines a set of constants for JPEG color spaces:

    // IJG Color codes.
    public static final int JCS_UNKNOWN = 0;       // error/unspecified
    public static final int JCS_GRAYSCALE = 1;     // monochrome
    public static final int JCS_RGB = 2;           // red/green/blue
    public static final int JCS_YCbCr = 3;         // Y/Cb/Cr (also known as YUV)
    public static final int JCS_CMYK = 4;          // C/M/Y/K
    public static final int JCS_YCC = 5;           // PhotoYCC
    public static final int JCS_RGBA = 6;          // RGB-Alpha
    public static final int JCS_YCbCrA = 7;        // Y/Cb/Cr/Alpha
    // 8 and 9 were old "Legacy" codes which the old code never identified
    // on reading anyway.  Support for writing them is being dropped, too.
    public static final int JCS_YCCA = 10;         // PhotoYCC-Alpha
    public static final int JCS_YCCK = 11;         // Y/Cb/Cr/K 

On the native code, these constants are defined in jpeglib.h as:

typedef enum {
        JCS_UNKNOWN,            /* error/unspecified */
        JCS_GRAYSCALE,          /* monochrome */
        JCS_RGB,                /* red/green/blue */
        JCS_YCbCr,              /* Y/Cb/Cr (also known as YUV) */
        JCS_CMYK,               /* C/M/Y/K */
        JCS_YCCK                /* Y/Cb/Cr/K */
} J_COLOR_SPACE;

The values of these constants in C is implied by the order of the enum declaration, as 0, 1, 2, 3, 4 and 5.

0 to 4 match with the Java constracts, but JCS_YCCK == 11 in Java and == 5 in the native code.

When the native code is about to compress and write a JPEG file, it checks the color space to make sure that it is in range, and it does so by checking for:

inCs > JCS_YCCK or outCs > JCS_YCCK

If the Java code passes Java's JCS_YCCK (a valid value), this fails validation in the native code, even though it should be fine.  i.e. Java passes 11, which is not < 5, and therefore an exception is thrown.

The validation and exception in the C code happens in imageioJPEG.c ->Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage().

Resolution is simple (and good programming practice), the enum definition in the native code should use the option to hard code the constant values, to make them match the constant values in the JPEG code:

typedef enum {
        JCS_UNKNOWN = 0,            /* error/unspecified */
        JCS_GRAYSCALE = 1,          /* monochrome */
        JCS_RGB = 2,                /* red/green/blue */
        JCS_YCbCr = 3,              /* Y/Cb/Cr (also known as YUV) */
        JCS_CMYK = 4,               /* C/M/Y/K */
        JCS_YCCK = 11                /* Y/Cb/Cr/K */
} J_COLOR_SPACE;



ADDITIONAL REGRESSION INFORMATION: 
This was working correctly in Java 6.  The reason is that Java 6 native code had additional entries in the enum definition, and therefore the constant values matched those in Java.

The values were still implied, but because they were the same number of constants in the enum, they happened to match.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a CMYK image in Java and try to write it as a JPEG using ImageIO.  We can provide source code if necessary that works in Java 6, but not in Java 7 or Java 8.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The JPEG image should be written out without errors.
ACTUAL -
Exception is thrown by native method.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
IIOException: Invalid argument to native writeImage

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
When a BufferedImage is passed to this method of type CMYK:

    public static void saveCMYKJPEG(OutputStream outStream, BufferedImage image, int xdpi, int ydpi, float quality) throws PDFException, IOException
    {
        // get a JPEG writer
        Iterator encoders = ImageIO.getImageWritersByFormatName("JPEG");
        if (encoders.hasNext() == false)
        {
            throw new PDFException ("No JPEG Writers Available.");
        }
        
        // Get the first writer
        ImageWriter writer = (ImageWriter)encoders.next();
        ImageWriteParam param = writer.getDefaultWriteParam();
        
        // Set compression quality
        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        param.setCompressionQuality(quality);

        ImageTypeSpecifier imageType = ImageTypeSpecifier.createFromRenderedImage(image);
        param.setDestinationType(imageType);
        
       // Write the image
        writer.setOutput(new MemoryCacheImageOutputStream (outStream));
        writer.write(null, new IIOImage(image.getRaster(), null, null), param);
    }

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
None found.