Reputation: 530
I would like to convert images using ICC profiles and Java. The source image can be RGB or CMYK based, and maybe could have an icc profile embeded. The new images should also be in RGB or CMYK color mode, and has to have a icc profile embeded. All source images will be in JPEG format.
Which (external/internal) Java framework/Library should I use ? What approche is the best to use for RGB > CMYK or RGB > RGB or CMYK > CMYK or CMYK > RGB icc based image conversion ?
Are there memory limitations ? Can it handle larger image files ? And how to handle the difference between sRGB an AdobeRGB ?
Does anyone have experience in handling image conversions using Java ?
Thanx
Upvotes: 3
Views: 3268
Reputation: 195
The above solution (by @Rohit) did not work for me. instance.fromRGB()
returns all zeros when used like that on my system on some real-world CMYK color profile (U.S. Web Coated (SWOP) v2).
This is the solution I created instead.
First of all I use custom sRGB color space implementation. You can find the reasons for that in this answer.
Custom sRGB color space implementation
package org.example;
import java.awt.color.ColorSpace;
public class SRGBColorSpace extends ColorSpace {
private static SRGBColorSpace sRGBColorSpace;
private SRGBColorSpace() {
super(ColorSpace.TYPE_RGB, 3);
}
public static SRGBColorSpace getInstance() {
if (sRGBColorSpace == null) {
sRGBColorSpace = new SRGBColorSpace();
}
return sRGBColorSpace;
}
@Override
public float[] toRGB(float[] colorValue) {
return colorValue;
}
@Override
public float[] fromRGB(float[] rgbValue) {
return rgbValue;
}
private float fTo(float t) {
if (t <= 0.04045f) {
return t / 12.92f;
} else {
return (float) Math.pow((t + 0.055) / 1.055, 2.4);
}
}
@Override
public float[] toCIEXYZ(float[] colorValue) {
// Also normalize RGB values here.
float r = fTo(colorValue[0] / 255.0f);
float g = fTo(colorValue[1] / 255.0f);
float b = fTo(colorValue[2] / 255.0f);
// Use D50 chromatically adapted matrix here as Photoshop does.
float X = (0.4360747f * r) + (0.3850649f * g) + (0.1430804f * b);
float Y = (0.2225045f * r) + (0.7168786f * g) + (0.0606169f * b);
float Z = (0.0139322f * r) + (0.0971045f * g) + (0.7141733f * b);
return new float[] {X, Y, Z};
}
private float fFrom(float t) {
if (t > 0.0031308f) {
return (1.055f * ((float) Math.pow(t, 1 / 2.4))) - 0.055f;
} else {
return t * 12.92f;
}
}
@Override
public float[] fromCIEXYZ(float[] colorValue) {
float X = colorValue[0];
float Y = colorValue[1];
float Z = colorValue[2];
// Use D50 chromatically adapted matrix as Photoshop does.
float tR = (3.1338561f * X) + (-1.6168667f * Y) + (-0.4906146f * Z);
float tG = (-0.9787684f * X) + (1.9161415f * Y) + (0.0334540f * Z);
float tB = (0.0719453f * X) + (-0.2289914f * Y) + (1.4052427f * Z);
float r = fFrom(tR) * 255.0f;
float g = fFrom(tG) * 255.0f;
float b = fFrom(tB) * 255.0f;
return new float[] {r, g, b};
}
}
Use with CMYK color profile like this
package org.example;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
String path = "/Library/Application Support/Adobe/Color/Profiles/Recommended/USWebCoatedSWOP.icc";
ColorSpace cmyk;
try {
cmyk = new ICC_ColorSpace(ICC_Profile.getInstance(path));
ColorSpace srgb = SRGBColorSpace.getInstance();
float[] srgbColors = new float[] {118, 48, 25};
float[] cmykColors = cmyk.fromCIEXYZ(srgb.toCIEXYZ(srgbColors));
System.out.println("CMYK: " + Math.round(cmykColors[0] * 100) + " " + Math.round(cmykColors[1] * 100) + " " + Math.round(cmykColors[2] * 100) + " " + Math.round(cmykColors[3] * 100) + "\n");
} catch (IOException e) {
System.out.println(e);
}
}
}
This gives the following result: CMYK: 33 84 97 41
. Adobe Photoshop gives 33 83 98 40
for the same conversion from sRGB IEC61966-2.1 (RGB) to U.S. Web Coated (SWOP) v2 (CMYK).
Upvotes: 0
Reputation: 27084
Warning: The question is vague/very wide, so the answer will have to be as well. But here's an attempt at an answer anyway.
The core Java 2D classes have good support for ICC profiles. See ICC_Profile
and ICC_ColorSpace
.
You can use ColorConvertOp
to convert between color spaces (or color profiles). It supports both BufferedImage
s and WritableRaster
s, and is generally fast for ICC conversions (uses native libraries behind the scenes). It will handle all the conversions you mentioned, including sRGB and AdobeRGB differences.
So far, so good.
To read and write JPEG files using ICC profiles, things get a little more complicated. In theory, the standard ImageIO
API is all you need. You'll need to obtain both the meta data (IIOMetadata
) and the pixels (as either Raster
or BufferedImage
).
The ICC profile will be part of the meta data. You need to obtain the native JPEG metadata specified here. For RGB JPEGs this is all you need.
Unfortunately, the standard JPEGImageReader
that is part of all Oracle or OpenJDK JREs does not support reading CMYK data as a BufferedImage
. You will have to read as Raster
. The raster will usually contain YCbCr or YCCK data, depending on the color transformation used when writing, so you might have to handle that transformation as well. Alternatively, you can use my JPEG plugin for ImageIO which supports CMYK JPEGs directly.
Writing is pretty much the same. Writing RGB is "straight forward" (although it takes a few lines of code), while writing CMYK is harder. The important thing is to use the meta data obtained from the image you read, then replace the ICC profile with the one you have converted to. And finally write the meta data and the updated image data back. My JPEG plugin can also write CMYK JPEGs.
For everything Java 2D and BufferedImage
/Raster
-related operations, you are normally limited by the available (heap) memory (as well as the maximum array size in Java, should you have that much RAM). There are some workarounds, feel free to look at my MappedImageFactory
and MappedFileBuffer
classes here for nio MappedByteBuffer
(memory mapped file buffer) backed data buffers for BufferedImage
s. I'm sure JAI also have some options for this, but I generally don't recommend JAI for new code, as it seems completely left in the dark by Oracle.
Good luck! Prepare to spend some time on this, though. 😀
Upvotes: 6
Reputation: 51
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.IOException;
import java.util.Arrays;
public class ColorConv {
final static String pathToCMYKProfile = "C:\\UncoatedFOGRA29.icc";
public static float[] rgbToCmyk(float... rgb) throws IOException {
if (rgb.length != 3) {
throw new IllegalArgumentException();
}
ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(pathToCMYKProfile));
float[] fromRGB = instance.fromRGB(rgb);
return fromRGB;
}
public static float[] cmykToRgb(float... cmyk) throws IOException {
if (cmyk.length != 4) {
throw new IllegalArgumentException();
}
ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(pathToCMYKProfile));
float[] fromRGB = instance.toRGB(cmyk);
return fromRGB;
}
public static void main(String... args) {
try {
float[] rgbToCmyk = rgbToCmyk(1.0f, 1.0f, 1.0f);
System.out.println(Arrays.toString(rgbToCmyk));
System.out.println(Arrays.toString(cmykToRgb(rgbToCmyk[0], rgbToCmyk[1], rgbToCmyk[2], rgbToCmyk[3])));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Upvotes: 4