简体   繁体   English

任何更快的从RGB转换为CMYK的算法

[英]Any faster algorithm to transform from RGB to CMYK

This is how I am doing to convert from RGB to CMYK using the more "correct" way - ie using an ICC color profile. 这就是我使用更“正确”的方式从RGB转换为CMYK的方式 - 即使用ICC颜色配置文件。

// 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. 在一种情况下,我花了大约104s而不是通常的2s来将数据写成JPEG图像。 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: 更新 :根据haraldK的建议,以下是修订版:

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. 更新:我还发现在BufferedImage而不是Raster上进行过滤要快得多。 See this post: ARGB int array to CMYKA byte array convertion 看到这篇文章: ARGB int数组到CMYKA字节数组转换

You should get rid of the memory allocation within the innermost loop. 你应该摆脱最内层循环中的内存分配。 new is a prohibitively expensive operation. new是一个非常昂贵的操作。 Also it might kick the garbage collector into action, which adds a further penality. 此外,它可能会使垃圾收集器付诸行动,这进一步增加了恶意。

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. 只有在您可以为多个图像重复使用查找表时,它才有用,但正如您的示例所示,您实际上反复使用相同的ICC配置文件。 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;
        }
    }
}

You should probably use ColorConvertOp . 您应该使用ColorConvertOp It uses optimized native code on most platforms, and supports ICC profile transforms. 它在大多数平台上使用优化的本机代码,并支持ICC配置文件转换。

Not sure how fast it will work when using float based Raster s, but it does the job. 不确定使用基于floatRaster时它的工作速度有多快,但它可以完成这项工作。

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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM