Создайте изображение RGB из каждого канала

У меня есть 3 файла, один только с красным каналом, один только с зеленым каналом, один только с синим каналом. Теперь я хочу объединить эти 3 изображения в одно, где каждое изображение представляет собой один цветовой канал в готовом изображении.

Как я могу сделать это с какао? У меня есть решение, которое работает, но слишком медленно:

NSBitmapImageRep *rRep = [[rImage representations] objectAtIndex: 0];
NSBitmapImageRep *gRep = [[gImage representations] objectAtIndex: 0];
NSBitmapImageRep *bRep = [[bImage representations] objectAtIndex: 0];
NSBitmapImageRep *finalRep = [rRep copy];
for (NSUInteger i = 0; i < [rRep pixelsWide]; i++) {
    for (NSUInteger j = 0; j < [rRep pixelsHigh]; j++) {
        CGFloat r = [[rRep colorAtX:i y:j] redComponent];
        CGFloat g = [[gRep colorAtX:i y:j] greenComponent];
        CGFloat b = [[bRep colorAtX:i y:j] blueComponent];
        [finalRep setColor:[NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0] atX:i y:j];
    }
}
NSData *data = [finalRep representationUsingType:NSJPEGFileType properties:[NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:0.7] forKey:NSImageCompressionFactor]];
[data writeToURL:[panel URL] atomically:YES];

person thomasguenzel    schedule 16.07.2013    source источник


Ответы (2)


Accelerate.framework предоставляет функцию для объединения 3 плоских изображений в одно место назначения: vImageConvert_Planar8toRGB888.
Я не пробовал ваш подход, но описанный ниже метод на основе vImage довольно быстр.
Мне удалось объединить три (R, G, B) плоскости изображения 1680x1050 за ~ 0,1 с на моем Mac. Фактическое преобразование занимает ~ 1/3 этого времени - остальное - установка и файловый ввод-вывод.

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSDate* start = [NSDate date];

    NSURL* redImageURL = [[NSBundle mainBundle] URLForImageResource:@"red"];
    NSURL* greenImageURL = [[NSBundle mainBundle] URLForImageResource:@"green"];
    NSURL* blueImageURL = [[NSBundle mainBundle] URLForImageResource:@"blue"];
    NSData* redImageData = [self newChannelDataFromImageAtURL:redImageURL];
    NSData* greenImageData = [self newChannelDataFromImageAtURL:greenImageURL];
    NSData* blueImageData = [self newChannelDataFromImageAtURL:blueImageURL];

    //We use our "Red" image to measure the dimensions. We assume that all images & the destination have the same size
    CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)redImageURL, NULL);
    NSDictionary* properties = (__bridge NSDictionary*)CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
    CGFloat width = [properties[(id)kCGImagePropertyPixelWidth] doubleValue];
    CGFloat height = [properties[(id)kCGImagePropertyPixelHeight] doubleValue];

    self.image = [self newImageWithSize:CGSizeMake(width, height) fromRedChannel:redImageData greenChannel:greenImageData blueChannel:blueImageData];
    NSLog(@"Combining 3 (R, G, B) planes of size %@ took:%fs", NSStringFromSize(CGSizeMake(width, height)), [[NSDate date] timeIntervalSinceDate:start]);
}

- (NSImage*)newImageWithSize:(CGSize)size fromRedChannel:(NSData*)redImageData greenChannel:(NSData*)greenImageData blueChannel:(NSData*)blueImageData
{
    vImage_Buffer redBuffer;
    redBuffer.data = (void*)redImageData.bytes;
    redBuffer.width = size.width;
    redBuffer.height = size.height;
    redBuffer.rowBytes = [redImageData length]/size.height;

    vImage_Buffer greenBuffer;
    greenBuffer.data = (void*)greenImageData.bytes;
    greenBuffer.width = size.width;
    greenBuffer.height = size.height;
    greenBuffer.rowBytes = [greenImageData length]/size.height;

    vImage_Buffer blueBuffer;
    blueBuffer.data = (void*)blueImageData.bytes;
    blueBuffer.width = size.width;
    blueBuffer.height = size.height;
    blueBuffer.rowBytes = [blueImageData length]/size.height;

    size_t destinationImageBytesLength = size.width*size.height*3;
    const void* destinationImageBytes = valloc(destinationImageBytesLength);
    NSData* destinationImageData = [[NSData alloc] initWithBytes:destinationImageBytes length:destinationImageBytesLength];
    vImage_Buffer destinationBuffer;
    destinationBuffer.data = (void*)destinationImageData.bytes;
    destinationBuffer.width = size.width;
    destinationBuffer.height = size.height;
    destinationBuffer.rowBytes = [destinationImageData length]/size.height;

    vImage_Error result = vImageConvert_Planar8toRGB888(&redBuffer, &greenBuffer, &blueBuffer, &destinationBuffer, 0);
    NSImage* image = nil;
    if(result == kvImageNoError)
    {
        //TODO: If you need color matching, use an appropriate colorspace here
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)(destinationImageData));
        CGImageRef finalImageRef = CGImageCreate(size.width, size.height, 8, 24, destinationBuffer.rowBytes, colorSpace, kCGBitmapByteOrder32Big|kCGImageAlphaNone, dataProvider, NULL, NO, kCGRenderingIntentDefault);
        CGColorSpaceRelease(colorSpace);
        CGDataProviderRelease(dataProvider);
        image = [[NSImage alloc] initWithCGImage:finalImageRef size:NSMakeSize(size.width, size.height)];
        CGImageRelease(finalImageRef);
    }
    free((void*)destinationImageBytes);
    return image;
}

- (NSData*)newChannelDataFromImageAtURL:(NSURL*)imageURL
{
    CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);
    if(imageSource == NULL){return NULL;}
    CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    CFRelease(imageSource);
    if(image == NULL){return NULL;}
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image);
    CGFloat width = CGImageGetWidth(image);
    CGFloat height = CGImageGetHeight(image);
    size_t bytesPerRow = CGImageGetBytesPerRow(image);
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
    CGContextRef bitmapContext = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, bitmapInfo);
    NSData* data = NULL;
    if(NULL != bitmapContext)
    {
        CGContextDrawImage(bitmapContext, CGRectMake(0.0, 0.0, width, height), image);
        CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
        if(NULL != imageRef)
        {
            data = (NSData*)CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef)));
        }
        CGImageRelease(imageRef);
        CGContextRelease(bitmapContext);
    }
    CGImageRelease(image);
    return data;
}
person Thomas Zoechling    schedule 16.07.2013
comment
Благодарность! именно то, что я искал :) еще один вопрос: на некоторых изображениях есть все 3 канала, но мне нужен только один. Как я могу это сделать? - person thomasguenzel; 16.07.2013
comment
Откуда берутся изображения вашего канала? Изображения, которые я использовал для тестирования вышеуказанного метода, уже были в сером цветовом пространстве. Есть несколько вопросов SO, посвященных этой теме, например. (не пробовал): stackoverflow.com/questions/3340564/ - person Thomas Zoechling; 16.07.2013
comment
vImage_Buffer redBuffer2; красныйбуфер2.ширина = размер.ширина; redBuffer2.height = размер.высота; redBuffer2.rowBytes = размер.ширина; vImageConvert_RGB888toPlanar8(&redBuffer, &redBuffer2, nil, nil, kvImageNoFlags); я добавил это ниже redBuffer.rowBytes = [redImageData length]/size.height; но это дает мне EXC_BAD_ACCESS, что я делаю неправильно? - person thomasguenzel; 16.07.2013

Ваша программа создает много-много-много-много-много цветных объектов.

Хотя ваша программа может просто получить доступ к представлениям изображения bitmapData, для этого потребуется, чтобы ваша программа много знала о представлениях растровых изображений.

Прежде чем использовать этот подход, вы должны позволить Quartz сделать тяжелую работу, визуализируя каждое изображение в CGBitmapContext (например, используя CGContextDrawImage(gtx, rect, img.CGImage)), а затем извлекая/копируя значения визуализированных компонентов из результата визуализации в целевое растровое изображение RGB.

Если ваши входные данные не являются многокомпонентными цветовыми моделями (например, в оттенках серого), вам следует выполнить рендеринг в исходную цветовую модель, чтобы сэкономить кучу процессорного времени и памяти.

person justin    schedule 16.07.2013