用 CMYK 颜色 space 在 java 中写入图像

Write image in java with CMYK color space

我想在 java 中使用 CMYK 颜色 space 编写图像,如下所示:

BufferedImage image= new BufferedImage(path.getBounds().width, 
path.getBounds().height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
color c = new Color(ColorSpaces.getDeviceCMYKColorSpace(), new float[] 
{1.0f,1.0f,1.0f,1.0f}, 1.0f)
g2d.setPaint(c);
g2d.fill(path);     
g2d.draw(path);
g2d.dispose();

然后我使用 imageIO 将图像写入 JPEG,但是,生成的图像在转换为 PDF 时没有我在代码中提供的 CMYK 颜色,而是具有以下

我的问题是:

  1. 我可以使用 BufferedImage 写入 CMYK 颜色的图像 space 吗?
  2. 如果组件值为 0,0,0,1.0 且 alpha 为 1,我如何使用 CMYK 颜色写入图像 space

请注意,我已经尝试过 EPS,它运行良好,但是,在我的项目的这个阶段,我对使用 EPS 感到不舒服。

  1. 是的,你可以。您需要在 CMYK 颜色 space 中创建一个 BufferedImage。然后在上面画画。您不能使用任何标准 BufferedImage.TYPE_* 类型,因为它们都是灰色或 RGB。请参阅下面的代码。

  2. 您可以使用 ImageIO 编写 CMYK JPEG。但是,您需要向元数据添加一些额外的细节并稍微修改像素数据才能执行此操作,否则图像将被写入为 RGBA 或反转 CMYK。同样,请参阅下面的代码。

这是一个完整的、可运行的概念验证代码示例:

public class CMYKTest {

    public static final String JAVAX_IMAGEIO_JPEG_IMAGE_1_0 = "javax_imageio_jpeg_image_1.0";

    public static void main(String[] args) throws IOException {
        // I'm using my own TwelveMonkeys ImageIO library for this, 
        // but I think you can use the one you used above, like:
        // ColorSpace cmykCS = ColorSpaces.getDeviceCMYKColorSpace()
        ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);

        // Create CMYK color model, raster and image
        ColorModel colorModel = new ComponentColorModel(cmykCS, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
        BufferedImage image = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(100, 100), colorModel.isAlphaPremultiplied(), null);

        // Paint some sample rectangles on it
        Graphics2D g = image.createGraphics();
        try {
            g.setColor(new Color(cmykCS, new float[] {0, 0, 0, 0}, 1.0f)); // All 0 (White)
            g.fillRect(0, 0, 25, 50);
            g.setColor(new Color(cmykCS, new float[] {0, 0, 0, 1}, 1.0f)); // Key (Black)
            g.fillRect(25, 0, 25, 50);
            g.setColor(new Color(cmykCS, new float[] {1, 0, 0, 0}, 1.0f)); // Cyan
            g.fillRect(50, 0, 50, 50);
            g.setColor(new Color(cmykCS, new float[] {0, 1, 0, 0}, 1.0f)); // Magenta
            g.fillRect(0, 50, 50, 50);
            g.setColor(new Color(cmykCS, new float[] {0, 0, 1, 0}, 1.0f)); // Yellow
            g.fillRect(50, 50, 50, 50);
        }
        finally {
            g.dispose();
        }

        // Write it as a JPEG, using ImageIO    
        try (ImageOutputStream stream = ImageIO.createImageOutputStream(new File("cmyk.jpg"))) {
            ImageWriter writer = ImageIO.getImageWritersByFormatName("JPEG").next();
            writer.setOutput(stream);

            // We need to massage the image metadata a little to be able to write CMYK
            ImageWriteParam param = writer.getDefaultWriteParam();
            IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), param);

            IIOMetadataNode jpegMeta = new IIOMetadataNode(JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
            jpegMeta.appendChild(new IIOMetadataNode("JPEGVariety")); // Just leave as default

            IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
            jpegMeta.appendChild(markerSequence);

            // The APP14 "Adobe" marker acts as a trigger for decoders, to
            // specify 4 channels as CMYK or YCCK (instead of RGBA or YCCA).
            IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe");
            app14Adobe.setAttribute("transform", "0"); // 0 means "unknown"
            markerSequence.appendChild(app14Adobe);

            // You could also append an ICC profile as part of the JPEG metadata
            // if you feel adventurous...

            // Merge with metadata from the writer
            metadata.mergeTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0, jpegMeta);

            // Also, we need to massage the raster a little, as CMYK data is
            // written in "inverse" form. 
            // We could use image.getRaster() here to get better performance
            // if you don't mind the image being inverted in memory too. 
            // image.getData() creates a copy, and is safe from this side effect.
            Raster raster = image.getData(); 
            byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
            // Inverse the pixel data
            for (int i = 0; i < data.length; i++) {
                data[i] = (byte) (255 - data[i]);
            }

            // Finally, write it all
            writer.write(null, new IIOImage(raster, null, metadata), param);
        }
    }
}

使用ImageIO读取CMYK JPEG图像的注意事项

以上代码将写入标准 JPEGImageReader 无法读取的 JPEG 图像(除了通过 readRaster() 方法)。

对于 better/easier CMYK JPEG 支持,我建议使用 TwelveMonkeys ImageIO JPEG 插件(我是该插件的作者)。

关于 CMYK 和透明度的小字

您还可以创建具有透明度(alpha 通道)的 CMYK 颜色 model/image,如下所示:

ColorModel colorModel = new ComponentColorModel(cmykCS, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(100, 100), colorModel.isAlphaPremultiplied(), null);

但是,不幸的是,JPEGImageWriter 无法处理超过 4 个数据通道。我在一个数组中得到一个 IndexArrayOutOfBoundsException,它有注释(IJG 是开发 libjpeg 的 Independent JPEG Group):

/** IJG can handle up to 4-channel JPEGs */

所以,不幸的是,没有简单的解决方法。