JWWalker
JWWalker

Reputation: 22707

How can I get the average color of a CIImage without errors

I'm trying to get the average color of a CIImage, using code that is similar to that of this question, but I get error messages in the console that say

Cannot render image (with an input GL texture) using a metal-DG context.
Failed to render 1 pixels because a CIKernel's ROI function did not allow tiling.

I guess the CIImage originally comes from OpenGL, but where did I ask for a metal context? How can I avoid this error?

My code:

CIFilter*   aveFilter = [CIFilter filterWithName: @"CIAreaAverage"];
[aveFilter setValue: inputImage forKey: @"inputImage"];

CGRect  srcExtent = [inputImage extent];
CIVector* extentVec = [CIVector
    vectorWithX: srcExtent.origin.x
    Y: srcExtent.origin.y
    Z: srcExtent.size.width
    W: srcExtent.size.height ];
[aveFilter setValue: extentVec forKey: @"inputExtent"];
CIImage*    aveColorPixel = [aveFilter valueForKey: @"outputImage"];

CIContext* bitContext = [[[CIContext alloc]
    initWithOptions: @{
        kCIContextWorkingColorSpace: [NSNull null],
        kCIContextWorkingFormat: @(kCIFormatRGBA8)
    }] autorelease];

unsigned char bitmap[4];
[bitContext render: aveColorPixel
            toBitmap: bitmap
            rowBytes: 4
            bounds: CGRectMake( 0.0, 0.0, 1.0, 1.0 )
            format: kCIFormatRGBA8
            colorSpace: nil];

I tried some other things like creating a NSBitmapImageRep from the CIImage and using -[NSBitmapImageRep colorAtX: y:] but get similar errors.


Things are a bit more complicated than I initially thought. I'm doing this in the Objective-C part of a Core Image filter, and it turns out that my averaging code works every other time the filter gets called. When it succeeds, there is a partial backtrace

frame #3: 0x00007fff41a35351 QuartzCore`CA::OGL::apply_cifilter(CA::Render::Filter const*, void const*) + 123
frame #4: 0x00007fff419cbc34 QuartzCore`CA::OGL::emit_filter(CA::OGL::Renderer&, CA::OGL::Filter const&, CA::OGL::Layer const&, float, CA::OGL::Surface*, float, CA::Vec2<float>, CA::Shape const*, float*) + 1678
frame #5: 0x00007fff41949ddd QuartzCore`CA::OGL::FilterNode::apply(float, CA::OGL::Surface**, float*) + 673
frame #6: 0x00007fff41936889 QuartzCore`CA::OGL::ImagingNode::render(CA::OGL::ImagingNode::RenderClosure*, unsigned int) + 473
frame #7: 0x00007fff4193edad QuartzCore`CA::OGL::ImagingNode::retain_surface(float&, unsigned int) + 171
frame #8: 0x00007fff41936743 QuartzCore`CA::OGL::ImagingNode::render(CA::OGL::ImagingNode::RenderClosure*, unsigned int) + 147

whereas when it fails and shows a console log error message, the partial backtrace is:

frame #3: 0x00007fff41a35351 QuartzCore`CA::OGL::apply_cifilter(CA::Render::Filter const*, void const*) + 123
frame #4: 0x00007fff41942cd3 QuartzCore`CA::OGL::measure_filter(CA::OGL::Renderer&, CA::OGL::Filter const&, CA::OGL::Layer const&, CA::OGL::Gstate const&, CA::Bounds const&, CA::Bounds&) + 267
frame #5: 0x00007fff41942b2c QuartzCore`CA::OGL::FilterNode::propagate_roi(CA::Bounds const&) + 58
frame #6: 0x00007fff41935c2b QuartzCore`CA::OGL::ImagingNode::set_roi(CA::Shape const*) + 405
frame #7: 0x00007fff419356d8 QuartzCore`CA::OGL::prepare_layers_roi(CA::OGL::Renderer&, CA::OGL::Layer*, CA::OGL::Gstate const&) + 974
frame #8: 0x00007fff41a42a3d QuartzCore`CA::OGL::LayerNode::prepare_sublayers_roi_if_needed() + 43
frame #9: 0x00007fff41937f08 QuartzCore`CA::OGL::LayerNode::apply(float, CA::OGL::Surface**, float*) + 150
frame #10: 0x00007fff41936889 QuartzCore`CA::OGL::ImagingNode::render(CA::OGL::ImagingNode::RenderClosure*, unsigned int) + 473

An answer suggests using a CIContext created from a GL context (not EAGL, this is macOS). Using this approach, I get a console error message "Cannot render image (with an input Metal texture) using a opengl context." when the backtrace mentions CA::OGL::emit_filter, but in either case I don't get a nonzero average color.

In my particular case, I do have a workaround. I wanted the average color to pass as a parameter to my filter's kernel. But instead I can pass the average color image to the kernel as a sampler, and let the kernel sample it to get the color. So it's probably not worth fighting with it.

Upvotes: 1

Views: 236

Answers (1)

Frank Rupprecht
Frank Rupprecht

Reputation: 10383

When you initialize a CIContext with init or initWithOptions:, it will be using Metal by default on all platforms now. You need to use contextWithEAGLContext:options: initializer and pass the same EGAGLContext that you used to create the texture of inputImage, then Core Image can work with that image.

Note, however, that OpenGL is deprecated on all platforms. So this might stop working in the near future.

Upvotes: 1

Related Questions