Any faster algorithm to transform from RGB to CMYK

帅比萌擦擦* 提交于 2020-01-23 18:21:07

问题


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 Rasters, 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!