给这个问题一些背景(ho ho):
我在 iOS 下对 CIFilter 进行子类化,以便创建一些自定义照片效果滤镜。根据文档,这意味着创建一个“复合”过滤器,将一个或多个预先存在的 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,我组装了一些工作代码示例,我将其发布在这里以帮助其他人:
通过编程生成CIColorCubeWithColorSpace CIFilter,在不同颜色管理方案/工作颜色空间下不变:
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);
文档指出:
要提供 CGColorSpaceRef 对象作为输入参数,请将其转换为 id 类型。使用默认颜色空间 (null)(相当于 kCGColorSpaceGenericRGBLinear),该滤镜的效果与 CIColorCube 的效果相同。
我想更进一步,能够从文件中读取cubeData。所谓的 Haled 颜色查找表,或保留 CLUT 图像可用于定义从输入颜色到输出颜色的映射。
在来自的帮助下this答案,我也组装了代码来执行此操作,为了方便起见,将其重新发布到此处。
Hald CLUT图像基于CIColorCubeWithColorSpace CIFilter,在不同颜色管理方案/工作色彩空间下不变:
Usage:
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, ÷r, &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 图像,然后对其应用与用于在任何图像编辑程序中可视化“外观”的图像相同的图像处理链。只需确保将示例代码中的cubeDimension 设置为LUT 图像的正确尺寸即可。如果尺寸 d 是 3D LUT 立方体一侧的元素数量,则 Hald CLUT 图像的宽度和高度将为 d*sqrt(d) 像素,并且图像的总像素为 d^3。