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

时间:2014-06-02 16:44:06

标签: java image transform rgb cmyk

这就是我如何使用更多"正确的"从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;
        }
    }
}

我的问题是:给定大图像时速度过慢。在一种情况下,我花了大约104s而不是通常的2s来将数据写成JPEG图像。事实证明,上述转换是最耗时的部分。

我想知道是否有办法让它更快。 注意:我不会使用可以从网上找到的廉价转换算法。

更新:根据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;
        }
    }
}

更新:我还发现在BufferedImage而不是Raster上进行过滤要快得多。请参阅此帖子:ARGB int array to CMYKA byte array convertion

3 个答案:

答案 0 :(得分:2)

你应该摆脱最内层循环中的内存分配。 new是一项非常昂贵的手术。此外,它可能会使垃圾收集器发挥作用,这进一步增加了恶意。

答案 1 :(得分:1)

如果您可以使用内存消耗,则可以创建一个查找表:

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

现在这可能实际上恶化小图像的性能。只有在您可以为多个图像重复使用查找表时才会有所帮助,但正如您的示例所示,您实际上反复使用相同的ICC配置文件。因此,您可以缓存查找表并仅支付其初始化成本一次:

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

答案 2 :(得分:0)

您应该使用ColorConvertOp。它在大多数平台上使用优化的本机代码,并支持ICC配置文件转换。

使用基于float Raster的{​​{1}}时,不确定它的工作速度有多快,但它可以完成任务。

类似的东西:

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