2

I've been trying for two days to find a way to perfectly convert a CMYK image to a RGB one in Java. I went through a lot of different ways to do it, all found on the Web, some of them on Stackoverflow, but I couldn't just find the way that would do it simply and without this awful color fading that is typical to such conversions. I know that tools like Photoshop or Irfanview do it perfectly in two clicks but I wanted it to be Java coded. Well, long story short, I found a way, and here it is.

Erwann
  • 95
  • 2
  • 11
  • To do this, remove the answer from your question and answer your question yourself. That is how it is meant to be if you would like to share your knowledge. – Martijn Courteaux Oct 23 '13 at 11:13
  • done. well, answer in 7 hours... – Erwann Oct 23 '13 at 11:23
  • Why within seven hours? Oh... Yes, when you create a question, you can mark it if you want to answer immediately. Maybe it is indeed impossible if you posted your question first, and then tries to add the solution. – Martijn Courteaux Oct 23 '13 at 11:29

2 Answers2

1

Thank you for your feedbacks.

Whome, I tried your way but it gave me either inverted or very strange colors whether I saved the image using ImageIO.write() or JAI.create().

haraldk, I haven't try your code yet. I had a look at it and it does not seem straightforward to me. I'll give it a try later.

Meanwhile, allow me to post my own way, that's actually made up of other people ways (this guy: https://stackoverflow.com/a/9470843/2435757 and that other guy: http://www.coderanch.com/t/485449/java/java/RGB-CMYK-Image, among others). It works although, as a new BufferedImage is created, information such as the resolution, or the compression method (for a TIFF image) are lost and must be reset, which this method does not (I think that the only non-JRE lib required here is Apache common xmlgraphics):

BufferedImage img = null;
try {
    img = ImageIO.read(new File("cmyk.jpg"));
} catch (IOException e) {}

ColorSpace cmyk = DeviceCMYKColorSpace.getInstance();
int w = img.getWidth(), h = img.getHeight();
BufferedImage image = null;
byte[] buffer = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
int pixelCount = buffer.length;
byte[] new_data = new byte[pixelCount / 4 * 3];
float lastC = -1, lastM = -1, lastY = -1, lastK = -1;
float C, M, Y, K;
float[] rgb = new float[3];
// loop through each pixel changing CMYK values to RGB
int pixelReached = 0;

for (int i = 0 ; i < pixelCount ; i += 4) {
    C = (buffer[i] & 0xff) / 255f;
    M = (buffer[i + 1] & 0xff) / 255f;
    Y = (buffer[i + 2] & 0xff) / 255f;
    K = (buffer[i + 3] & 0xff) / 255f;
    if (lastC == C && lastM == M && lastY == Y && lastK == K) {
        //use existing values if not changed
    } else { //work out new
        rgb = cmyk.toRGB(new float[] {C, M, Y, K});
        //cache values
        lastC = C;
        lastM = M;
        lastY = Y;
        lastK = K;
    }
    new_data[pixelReached++] = (byte) (rgb[0] * 255);
    new_data[pixelReached++] = (byte) (rgb[1] * 255);
    new_data[pixelReached++] = (byte) (rgb[2] * 255);
}

// turn data into RGB image
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
int[] l_bandoff = {0, 1, 2};
PixelInterleavedSampleModel l_sm = new PixelInterleavedSampleModel(DataBuffer.TYPE_INT, w, h, 3, w * 3, l_bandoff);
image.setData(new ByteInterleavedRaster(l_sm, new DataBufferByte(new_data, new_data.length), new Point(0, 0)));

// write
ImageIO.write(image, "jpg", new File("rgb.jpg"));

The above code gives me excellent results for both JPEG and TIFF images, although I happened to get a very strange result with a particular image.

Here is another, much simpler and straightforward, way by JMagick:

ImageInfo info = new ImageInfo("cmyk.tif");
MagickImage image = new MagickImage(info);
image.transformRgbImage(ColorspaceType.CMYKColorspace);
image.setFileName("rgb.tif");
image.writeImage(info);

Couldn't be shorter, could it? Also works like a charm for both JPEG and TIFF.

And no, haraldk, I didn't use any reference to a color profile. That seems quite weird to me too. I can only assume that both ways use a default color profile and that I've been lucky enough for it to work fine in all cases so far.

I am waiting for your feedbacks on this.

Cheers.

PS: I would be more than glad to give you links to the images I use, but Stackoverflow says I'm not reliable enough :-) In another post maybe, if you require them.

Community
  • 1
  • 1
Erwann
  • 95
  • 2
  • 11
0

What SO answers did you try and found not working properly?

Did any of them gave this example code. Does it create color fading? Would you please share an example image link creating a problem?

/**
 * ImageIO cannot read CMYK-jpegs, it throws IIOException(Unsupported Image Type).
 * This method tries to read cmyk image.
 * @param file
 * @return  image TYPE_4BYTE_ABGR
 * @throws Exception
 */
public static BufferedImage readCMYKImage(File file) throws Exception {
    Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
    ImageReader reader = null;
    while(readers.hasNext()) {
        reader = readers.next();
        if(reader.canReadRaster())
            break;
    }

    FileInputStream fis = new FileInputStream(file);
    try {
        ImageInputStream input = ImageIO.createImageInputStream(fis); 
        reader.setInput(input); // original CMYK-jpeg stream
        Raster raster = reader.readRaster(0, null); // read image raster 
        BufferedImage image = new BufferedImage(raster.getWidth(), raster.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
        image.getRaster().setRect(raster);
        return image;
    } finally {
        try { fis.close(); } catch(Exception ex) {}
    }
}
Whome
  • 10,181
  • 6
  • 53
  • 65
  • 1
    You can't do a proper conversion without getting the ICC color profiled usually embedded in a CMYK JPEG file. The code posted here just assumes that the CMYK data is ABGR, which will look nothing like the intent... I suggest you take a look at http://stackoverflow.com/a/12132805/1428606 and http://stackoverflow.com/a/16149142/1428606 :-) – Harald K Oct 23 '13 at 12:52