CIPhotoEffect CIFilters 在颜色管理方面是不变的。是什么让 CIPhotoEffect 滤镜如此 属性?

CIPhotoEffect CIFilters are ~invariant with respect to colour management. What gives CIPhotoEffect filters this property?

给这个问题一些背景(ho ho):

我将 CIFilter 子类化到 iOS 下,目的是创建一些自定义照片效果滤镜。根据 documentation,这意味着创建一个 "compound" 过滤器,将一个或多个预先存在的 CIFilter 封装在我的自定义 CIFilter 子类的范围内。

一切都很好。那里没有问题。举个例子,假设我封装了一个 CIColorMatrix 过滤器,它已经用某些 rgba 输入向量进行了预设。

当应用我的自定义过滤器(或实际上单独使用 CIColorMatrix)时,我看到在使用颜色管理打开和关闭的 CIContext 时截然不同的结果。我正在创建我的上下文如下:

颜色管理开启:

CIContext * context = [CIContext contextWithOptions:nil];

颜色管理关闭:

NSDictionary *options = @{kCIContextWorkingColorSpace:[NSNull null], kCIContextOutputColorSpace:[NSNull null]};
CIContext * context = [CIContext contextWithOptions:options];

现在,这并不奇怪。但是,我注意到所有预构建的 CIPhotoEffect CIFilters,例如CIPhotoEffectInstant,在相同的两种颜色管理条件下本质上是不变的。

任何人都可以提供任何关于是什么给了他们这个 属性 的见解吗?例如,它们本身是否封装了可能具有类似不变性的特定 CIFilter?

我的目标是创建一些具有相同 属性 的自定义滤镜,而不限于仅链接 CIPhotoEffect 滤镜。

--

编辑:感谢 YuAo,我整理了一些工作代码示例,我 post 在这里帮助其他人:

程序生成的CIColorCubeWithColorSpace CIFilter,在不同颜色管理方案/工作色下不变space:

    self.filter = [CIFilter filterWithName:@"CIColorCubeWithColorSpace"];
   [self.filter setDefaults];

    int cubeDimension = 2; // Must be power of 2, max 128
    int cubeDataSize = 4 * cubeDimension * cubeDimension * cubeDimension; // bytes
    float cubeDataBytes[8*4] = {
        0.0, 0.0, 0.0, 1.0,
        0.1, 0.0, 1.0, 1.0,
        0.0, 0.5, 0.5, 1.0,
        1.0, 1.0, 0.0, 1.0,
        0.5, 0.0, 0.5, 1.0,
        1.0, 0.0, 1.0, 1.0,
        0.0, 1.0, 1.0, 1.0,
        1.0, 1.0, 1.0, 1.0
    };

    NSData *cubeData = [NSData dataWithBytes:cubeDataBytes length:cubeDataSize * sizeof(float)];

    [self.filter setValue:@(cubeDimension) forKey:@"inputCubeDimension"];
    [self.filter setValue:cubeData forKey:@"inputCubeData"];
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    [self.filter setValue:(__bridge id)colorSpace forKey:@"inputColorSpace"];
    [self.filter setValue:sourceImageCore forKey:@"inputImage"];

    CIImage *filteredImageCore = [self.filter outputImage];
    CGColorSpaceRelease(colorSpace);

文档状态:

To provide a CGColorSpaceRef object as the input parameter, cast it to type id. With the default color space (null), which is equivalent to kCGColorSpaceGenericRGBLinear, this filter’s effect is identical to that of CIColorCube.

我想更进一步,能够从文件中读取 cubeData。所谓的 Hald 颜色查找表,或 Hald CLUT images 可用于定义从输入颜色到输出颜色的映射。

回答的帮助下,我也组装了代码来执行此操作,为方便起见,在此处重新post。

基于CLUT图像的CIColorCubeWithColorSpace CIFilter,在不同颜色管理方案/工作色下不变space:

用法:

    NSData *cubeData = [self colorCubeDataFromLUT:@"LUTImage.png"];
    int cubeDimension = 64;

    [self.filter setValue:@(cubeDimension) forKey:@"inputCubeDimension"];
    [self.filter setValue:cubeData forKey:@"inputCubeData"];
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); // or whatever your image's colour space
    [self.filter setValue:(__bridge id)colorSpace forKey:@"inputColorSpace"];
    [self.filter setValue:sourceImageCore forKey:@"inputImage"];

辅助方法(使用 Accelerate Framework):

- (nullable NSData *) colorCubeDataFromLUT:(nonnull NSString *)name
{
    UIImage *image = [UIImage imageNamed:name inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil];
    static const int kDimension = 64;

    if (!image) return nil;

    NSInteger width = CGImageGetWidth(image.CGImage);
    NSInteger height = CGImageGetHeight(image.CGImage);
    NSInteger rowNum = height / kDimension;
    NSInteger columnNum = width / kDimension;

    if ((width % kDimension != 0) || (height % kDimension != 0) || (rowNum * columnNum != kDimension)) {
        NSLog(@"Invalid colorLUT %@",name);
        return nil;
    }

    float *bitmap = [self createRGBABitmapFromImage:image.CGImage];
    if (bitmap == NULL) return nil;

    // Convert bitmap data written in row,column order to cube data written in x:r, y:g, z:b representation where z varies > y varies > x.
    NSInteger size = kDimension * kDimension * kDimension * sizeof(float) * 4;
    float *data = malloc(size);
    int bitmapOffset = 0;
    int z = 0;
    for (int row = 0; row <  rowNum; row++)
    {
        for (int y = 0; y < kDimension; y++)
        {
            int tmp = z;
            for (int col = 0; col < columnNum; col++) {
                NSInteger dataOffset = (z * kDimension * kDimension + y * kDimension) * 4;

                const float divider = 255.0;
                vDSP_vsdiv(&bitmap[bitmapOffset], 1, &divider, &data[dataOffset], 1, kDimension * 4); // Vector scalar divide; single precision. Divides bitmap values by 255.0 and puts them in data, processes each column (kDimension * 4 values) at once.

                bitmapOffset += kDimension * 4; // shift bitmap offset to the next set of values, each values vector has (kDimension * 4) values.
                z++;
            }
            z = tmp;
        }
        z += columnNum;
    }

    free(bitmap);

    return [NSData dataWithBytesNoCopy:data length:size freeWhenDone:YES];
}

- (float *)createRGBABitmapFromImage:(CGImageRef)image {
    CGContextRef context = NULL;
    CGColorSpaceRef colorSpace;
    unsigned char *bitmap;
    NSInteger bitmapSize;
    NSInteger bytesPerRow;

    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);

    bytesPerRow   = (width * 4);
    bitmapSize     = (bytesPerRow * height);

    bitmap = malloc( bitmapSize );
    if (bitmap == NULL) return NULL;

    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL) {
        free(bitmap);
        return NULL;
    }

    context = CGBitmapContextCreate (bitmap,
                                     width,
                                     height,
                                     8,
                                     bytesPerRow,
                                     colorSpace,
                                     (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease( colorSpace );

    if (context == NULL) {
        free (bitmap);
        return NULL;
    }

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
    CGContextRelease(context);

    float *convertedBitmap = malloc(bitmapSize * sizeof(float));
    vDSP_vfltu8(bitmap, 1, convertedBitmap, 1, bitmapSize); // Converts an array of unsigned 8-bit integers to single-precision floating-point values.
    free(bitmap);

    return convertedBitmap;
}

人们可以通过获取身份图像 (Google!) 创建一个 Hald CLUT 图像,然后对其应用相同的图像处理链,该链应用于用于可视化任何图像的 "look"图像编辑程序。只需确保将示例代码中的 cubeDimension 设置为 LUT 图像的正确尺寸。如果维度 d 是沿 3D LUT 立方体一侧的元素数,则 Hald CLUT 图像宽度和高度将为 d*sqrt(d) 像素,图像将具有 d^3 总像素。

CIPhotoEffect 内部使用 CIColorCubeWithColorSpace 过滤器。

所有颜色立方体数据都存储在CoreImage.framework.

您可以在此处找到模拟器的 CoreImage.framework (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CoreImage.framework/)。

颜色立方体数据以 scube 路径扩展名命名。例如CIPhotoEffectChrome.scube

CIColorCubeWithColorSpace 使用私有方法在内部隐藏颜色立方体颜色值以匹配当前核心图像上下文的工作颜色 space: -[CIImage _imageByMatchingWorkingSpaceToColorSpace:]; -[CIImage _imageByMatchingColorSpaceToWorkingSpace:];

以下是 CIPhotoEffect/CIColorCubeWithColorSpace 开启和关闭色彩管理的方式。

启用颜色管理后,CI 应该这样做:

  1. 从输入 space 到立方体 space 的颜色匹配。如果这两个相等,这是一个 noop。

  2. 应用颜色立方体。

  3. 从立方体 space 到输出 space 的颜色匹配。如果这两个相等,这是一个 noop。

关闭颜色管理后,CI 应该这样做:

  1. 应用颜色立方体。