问题
This is how I am doing to convert from RGB to CMYK using the more "correct" way - i.e using an ICC color profile.
// Convert RGB to CMYK with level shift (minus 128)
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(JPEGWriter.class.getResourceAsStream(pathToCMYKProfile)));
float red, green, blue, cmyk[];
//
for(int i = 0, index = 0; i < imageHeight; i++) {
for(int j = 0; j < imageWidth; j++, index++) {
red = ((rgb[index] >> 16) & 0xff)/255.0f;
green = ((rgb[index] >> 8) & 0xff)/255.0f;
blue = (rgb[index] & 0xff)/255.0f;
cmyk = instance.fromRGB(new float[] {red, green, blue});
C[i][j] = cmyk[0]*255.0f - 128.0f;
M[i][j] = cmyk[1]*255.0f - 128.0f;
Y[i][j] = cmyk[2]*255.0f - 128.0f;
K[i][j] = cmyk[3]*255.0f - 128.0f;
}
}
}
My problem is: it's prohibitively slow given a large image. In one case, it took about 104s instead of the usual 2s for me to write the data as a JPEG image. It turns out the above transform is the most time-consuming part.
I am wondering if there is any way to make it faster. Note: I am not going to use the cheap conversion algorithm one can find form the web.
Update: following haraldK's suggestion, here is the revised version:
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
if(cmykColorSpace == null)
cmykColorSpace = new ICC_ColorSpace(ICC_Profile.getInstance(JPEGWriter.class.getResourceAsStream(pathToCMYKProfile)));
DataBuffer db = new DataBufferInt(rgb, rgb.length);
WritableRaster raster = Raster.createPackedRaster(db, imageWidth, imageHeight, imageWidth, new int[] {0x00ff0000, 0x0000ff00, 0x000000ff}, null);
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorConvertOp cco = new ColorConvertOp(sRGB, cmykColorSpace, null);
WritableRaster cmykRaster = cco.filter(raster, null);
byte[] o = (byte[])cmykRaster.getDataElements(0, 0, imageWidth, imageHeight, null);
for(int i = 0, index = 0; i < imageHeight; i++) {
for(int j = 0; j < imageWidth; j++) {
C[i][j] = (o[index++]&0xff) - 128.0f;
M[i][j] = (o[index++]&0xff) - 128.0f;
Y[i][j] = (o[index++]&0xff) - 128.0f;
K[i][j] = (o[index++]&0xff) - 128.0f;
}
}
}
Update: I also found out it's much faster to do filter on a BufferedImage instead of a Raster. See this post: ARGB int array to CMYKA byte array convertion
回答1:
You should probably use ColorConvertOp. It uses optimized native code on most platforms, and supports ICC profile transforms.
Not sure how fast it will work when using float
based Raster
s, but it does the job.
Something like:
ICC_Profile cmyk = ...;
ICC_Profile sRGB = ...;
ColorConvertOp cco = new ColorConvertOp(sRGB, cmyk);
Raster rgbRaster = ...;
WritableRaster cmykRaster = cco.filter(rgbRaster, null);
// Or alternatively, if you have a BufferedImage input
BufferedImage rgbImage = ...;
BufferedImage cmykImage = cco.filter(rgbImage, null);
回答2:
You should get rid of the memory allocation within the innermost loop. new
is a prohibitively expensive operation. Also it might kick the garbage collector into action, which adds a further penality.
回答3:
If you can affort the memory consumption, you could create a lookup table:
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
ColorSpace cs = new ICC_ColorSpace(...);
int[] lookup = createRGB2CMYKLookup(cs);
for(int y = 0, index = 0; y < imageHeight; y++) {
for(int x = 0; x < imageWidth; x++, index++) {
int cmyk = lookup[rgb[index]];
C[y][x] = ((cmyk >> 24) & 255) - 128F;
M[y][x] = ((cmyk >> 16) & 255) - 128F;
Y[y][x] = ((cmyk >> 8) & 255) - 128F;
K[y][x] = ((cmyk ) & 255) - 128F;
}
}
}
static int[] createRGB2CMYKLookup(ColorSpace cs) {
int[] lookup = new int[16 << 20]; // eats 16m times 4 bytes = 64mb
float[] frgb = new float[3];
float fcmyk[];
for (int rgb=0; rgb<lookup.length; ++rgb) {
frgb[0] = ((rgb >> 16) & 255) / 255F;
frgb[1] = ((rgb >> 8) & 255) / 255F;
frgb[2] = ((rgb ) & 255) / 255F;
fcmyk = cs.fromRGB(frgb);
int c = (int) (fcmyk[0] * 255F);
int m = (int) (fcmyk[1] * 255F);
int y = (int) (fcmyk[2] * 255F);
int k = (int) (fcmyk[3] * 255F);
int icmyk = (c << 24) | (m << 16) | (y << 8) | k;
}
return lookup;
}
Now this may actually worsen performance for small images as it is. It will only help if you can re-use the lookup table for multiple images, but as your example looks you're using actually the same ICC profile over and over. Thus you could cache the lookup table and pay its initialization cost only once:
static int[] lookup;
static {
ColorSpace cs = new ICC_ColorSpace(...);
lookup = createRGB2CMYKLookup(cs);
}
// convert always using (the same) lookup table
private void RGB2CMYK(int[] rgb, float[][] C, float[][] M, float[][] Y, float[][] K, int imageWidth, int imageHeight) throws Exception {
for(int y = 0, index = 0; y < imageHeight; y++) {
for(int x = 0; x < imageWidth; x++, index++) {
int cmyk = lookup[rgb[index]];
C[y][x] = ((cmyk >> 24) & 255) - 128F;
M[y][x] = ((cmyk >> 16) & 255) - 128F;
Y[y][x] = ((cmyk >> 8) & 255) - 128F;
K[y][x] = ((cmyk ) & 255) - 128F;
}
}
}
来源:https://stackoverflow.com/questions/23999442/any-faster-algorithm-to-transform-from-rgb-to-cmyk