Чтобы дать этому вопросу некоторый контекст (хо-хо):
Я создаю подкласс CIFilter под iOS с целью создания некоторых пользовательских фильтров с фотоэффектами. Согласно документации, это означает создание «составного» фильтра, который инкапсулирует один или несколько ранее существовавших фильтров 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 из файла. Для определение сопоставления входного цвета с выходным цветом.
С помощью этого ответа я собрал код для выполнения это также размещено здесь для удобства.
Hald CLUT на основе CIColorCubeWithColorSpace CIFilter изображения CLUT, инвариантный при различных схемах управления цветом / рабочем цветовом пространстве:
Использование:
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;
}
Можно создать изображение Hald CLUT, получив изображение идентичности (Google!) И затем применив к нему ту же цепочку обработки изображений, которая применяется к изображению, используемому для визуализации «взгляда» в любой программе редактирования изображений. Просто убедитесь, что вы установили cubeDimension в примере кода на правильное измерение для изображения LUT. Если размер d - это количество элементов вдоль одной стороны куба 3D LUT, ширина и высота изображения Hald CLUT будут d * sqrt (d) пикселей, а общее количество пикселей изображения будет d ^ 3.