Search code examples
objective-cimagecocoaimage-processingvimage

vImageConvert to extract one channel


How can i "extract" only one channel from an image using vImage? My approach right now is:

vImage_Buffer redBuffer;
redBuffer.data = (void*)redImageData.bytes;
redBuffer.width = size.width;
redBuffer.height = size.height;
redBuffer.rowBytes = [redImageData length]/size.height;
vImage_Buffer redBuffer2;
redBuffer2.width = size.width;
redBuffer2.height = size.height;
redBuffer2.rowBytes = size.width;
vImageConvert_RGB888toPlanar8(&redBuffer, &redBuffer2, nil, nil, kvImageNoFlags);

redBuffer is a working image, no errors with it, but vImageConvert gives me an EXC_BAD_ACCESS, i've tried many options and some lead to an EXC_BAD_ACCESS and some to a broken output image. What am i doing wrong?

So thanks to Rob Keniger i've managed to extract the channels, but if i try to put them back together with other channels, the images get stretched horizontally and have RGB stripes in it. Why aren't they put back together correctly? Here an example with extracting the channels and then putting them back together:

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

    vImage_Buffer rBuffer;
    rBuffer.width = size.width;
    rBuffer.height = size.height;
    rBuffer.rowBytes = size.width;
    void *rPixelBuffer = malloc(size.width * size.height);
    if(rPixelBuffer == NULL)
    {
        NSLog(@"No pixelbuffer");
    }
    rBuffer.data = rPixelBuffer;

    vImage_Buffer gBuffer;
    gBuffer.width = size.width;
    gBuffer.height = size.height;
    gBuffer.rowBytes = size.width;
    void *gPixelBuffer = malloc(size.width * size.height);
    if(gPixelBuffer == NULL)
    {
        NSLog(@"No pixelbuffer");
    }
    gBuffer.data = gPixelBuffer;

    vImage_Buffer bBuffer;
    bBuffer.width = size.width;
    bBuffer.height = size.height;
    bBuffer.rowBytes = size.width;
    void *bPixelBuffer = malloc(size.width * size.height);
    if(bPixelBuffer == NULL)
    {
        NSLog(@"No pixelbuffer");
    }
    bBuffer.data = bPixelBuffer;

    vImageConvert_RGB888toPlanar8(&blueBuffer, &rBuffer, &gBuffer, &bBuffer, kvImageNoFlags);

    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(&rBuffer, &gBuffer, &bBuffer, &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;

Solution

  • You're not initialising the redBuffer2 output buffer, so you don't have anywhere to store the output of the function.

    //allocate a buffer for the output image and check it exists
    void *pixelBuffer = malloc(size.width * size.height);
    if(pixelBuffer == NULL)
    {
        NSLog(@"No pixelbuffer"); 
        //handle this somehow
    }
    
    //assign the allocated buffer to the vImage buffer struct
    redBuffer2.data = pixelBuffer;