How to get meaningful CIAreaHistogram output?
Asked Answered
C

7

7

I want to calculate the histogram of a CGImage. I am using the CIAreaHistogram built-in CoreImage filter.

Justin Mrkva has done something along similar lines. He says:

I get the CIImage for the histogram itself, which I then run through a custom kernel (see end of post) to set alpha values to 1 (since otherwise the alpha value from the histogram calculations is premultiplied) and then convert it to an NSBitmapImageRep.

My question is: is it possible to get the histogram data without having to create a custom kernel? If so, how?

The following code simply tries to render the histogram without chaning the alpha values:

- (void)printHistogram:(CGImageRef)img {

    NSNumber* buckets = @10;

    CIImage* img_ = [CIImage imageWithCGImage:img];

    CIFilter* histF = [CIFilter filterWithName:@"CIAreaHistogram"];
    [histF setValue:img_ forKey:@"inputImage"];
    [histF setValue:[CIVector vectorWithX:0.0
                                        Y:0.0
                                        Z:CGImageGetWidth(img)
                                        W:CGImageGetHeight(img)]
             forKey:@"inputExtent"];
    [histF setValue:buckets forKey:@"inputCount"];
    [histF setValue:@1.0 forKey:@"inputScale"];

    CIImage* histImg = [histF valueForKey:@"outputImage"];

    int rowBytes = [buckets intValue] * 4; // ARGB has 4 components
    uint8_t byteBuffer[rowBytes]; // Buffer to render into
    CGColorSpaceRef cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

    CIContext* ctx = [[CIContext alloc] init];
    [ctx render:histImg
       toBitmap:byteBuffer
       rowBytes:rowBytes
         bounds:[histImg extent]
         format:kCIFormatARGB8
     colorSpace:cspace];

    CGColorSpaceRelease(cspace);

    for (int i=0; i<[buckets intValue]; i++) {
        const uint8_t* pixel = &byteBuffer[i*4];
        printf("%d:%u-%u-%u-%u\n",i,pixel[0],pixel[1],pixel[2],pixel[3]);
    }

}   

Giving the output (when run on a color photo):

0:0-0-0-0
1:0-0-0-0
2:0-0-0-0
3:0-0-0-0
4:0-0-0-0
5:0-0-0-0
6:0-0-0-0
7:0-0-0-0
8:0-0-0-0
9:255-33-6-7

I tried using CIColorMatrix to set the alpha values to 1.0 before rendering:

CIFilter* biasF = [CIFilter filterWithName:@"CIColorMatrix"];
[biasF setDefaults];
[biasF setValue:histImg forKey:@"inputImage"];
[biasF setValue:[CIVector vectorWithX:0.0 Y:0.0 Z:0.0 W:1.0] forKey:@"inputBiasVector"];

Even though the output format is ARGB, from what I understand from the Core Image Filter Reference, the alpha component is the last value in the vector (thus W:1.0).

But this yielded the following output:

0:255-255-255-255
1:255-255-255-255
2:255-255-255-255
3:255-255-255-255
4:255-255-255-255
5:255-255-255-255
6:255-255-255-255
7:255-255-255-255
8:255-255-0-255
9:255-66-11-15

All help and advice will be much appreciated!


EDIT: I know this question seems similar. However, the accepted answer stipulates:

The short of it is: you need to read the values as floats, not ints, which means you'll have to hook up a CGBitmapContext to blit to. Or if you keep everything in CI land, you'll need another filter to read the data and print something out with it.

However, looking at Justin Mrkva's question makes me think that getting integer values should be possible... Please let me know if there is an error in my thinking.

Thanks again!


EDIT 2: Fist of all, thank you to both David and jstn for their comments. Sorry it took so long for me to come back to this. I was working around the clock on a project (in fact it was that project that led me to this problem, but I ended up using an altogether different approach that no longer utilizes CIAreaHistogram). Now that I finally have some time on my hands, I wanted to get back to this. Even though I don't need it per se, I still want to understand how this thing really works!

Following David Hayward's suggestions, I made the following modifications.

- (void)printHistogram:(CGImageRef)img {

    NSNumber* buckets = @10;

    CIImage* img_ = [CIImage imageWithCGImage:img];

    CIFilter* histF = [CIFilter filterWithName:@"CIAreaHistogram"];
    [histF setValue:img_ forKey:@"inputImage"];
    [histF setValue:[CIVector vectorWithX:0.0
                                        Y:0.0
                                        Z:CGImageGetWidth(img)
                                        W:CGImageGetHeight(img)]
             forKey:@"inputExtent"];
    [histF setValue:buckets forKey:@"inputCount"];
    [histF setValue:@1.0 forKey:@"inputScale"];

    CIImage* histImg = [histF valueForKey:@"outputImage"];

    NSUInteger arraySize = [buckets intValue] * 4; // ARGB has 4 components

    // CHANGE 1: Since I will be rendering in float values, I set up the
    //           buffer using CGFloat
    CGFloat byteBuffer[arraySize]; // Buffer to render into

    // CHANGE 2: I wasn't supposed to call [[CIContext alloc] init]
    //           this is a more proper way of getting the context
    CIContext* ctx = [[NSGraphicsContext currentContext] CIContext];

    // CHANGE 3: I use colorSpace:NULL to use the output cspace of the ctx
    // CHANGE 4: Format is now kCIFormatRGBAf
    [ctx render:histImg
       toBitmap:byteBuffer
       rowBytes:arraySize
         bounds:[histImg extent]
         format:kCIFormatRGBAf
     colorSpace:NULL]; // uses the output cspace of the contetxt

    // CHANGE 5: I print the float values
    for (int i=0; i<[buckets intValue]; i++) {
        const CGFloat* pixel = &byteBuffer[i*4];
        printf("%d: %0.2f , %0.2f , %0.2f , %0.2f\n", i,pixel[0],pixel[1],pixel[2],pixel[3]);
    }
}   

This gives the following output:

0: 0.00 , 0.00 , 0.00 , 0.00
1: 0.00 , 0.00 , 0.00 , 0.00
2: 0.00 , 0.00 , 0.00 , 0.00
3: 0.00 , 0.00 , 0.00 , 0.00
4: 0.00 , 0.00 , 0.00 , 0.00
5: 0.00 , 0.00 , 0.00 , 0.00
6: 0.00 , 0.00 , 1.00 , 0.00
7: 0.00 , 0.00 , 0.00 , 0.00
8: 0.00 , 0.00 , 0.00 , 0.00
9: 3.00 , 0.00 , 0.00 , 0.00

Playing around with variations of formats and how the information is parsed yields wildly different and absurd outputs.

I'm quite sure the trouble lies in not properly understanding precisely how the bitmap data is represented.

Any further suggestions?

Cluny answered 1/12, 2014 at 21:5 Comment(0)
J
1

Three suggestions:

  • use inputScale to gain the histogram counts up. If inputScale is 1, then a resulting histogram bin value will be 1.0 (255 if rendered to ARGB8) if the entire area has that bin value)
  • pass CI's working colorspace to render:toBitmap:. On Mavericks the working space is kCGColorSpaceGenericRGBLinear. On Yosemite it is linear sRGB.
  • on OS X you can use kCIFormatRGBAf to get float data
  • on iOS you can use kCIFormatRGBAh to get half-float data
Jett answered 27/1, 2015 at 10:56 Comment(3)
David, is there an alternative to using kCIFormatRGBAf for iOS since it's not available? Also is it normal to see high CPU usage with CIAreaHistogram, despite rendering with EAGLContext? I get about the same performance loading it up in CIFunHouse.Anthropomorphous
Thanks, I see you updated your answer and mentioned kCIFormatRGBAh, but it still doesn't work for me (on any core image, not just the histogram filter output.) I created a separate question here with a code sample, maybe you could take a look: #28416830Anthropomorphous
Dear David, thank you for your suggestions. I tried implementing some of them (see EDIT 2 above). I haven't been able to get it to work yet. By the way, I didn't understand your suggestion about using inputScale. The behavior you describe (1.0 = entire area covered) exactly what I want. For images that aren't monotonic in a given channel, none of the bins should be 1.0 but the sum should always be 1.0.Cluny
M
1

I worked on this for some time, but never got kCIFormatARGB8 to work. Another poster said that kCIFormatARGBf doesn't work on iOS, so I tried kCIFormatRGBAh, instead.

kCIFormatRGBAh is strange: it's RGBA data packed as 16-bit floats ("half-floats"). C does not have a native half-float type, and you must rely on compiler-specific data-types. Fortunately, both gcc and clang seem to support the type as __fp16.

// img is input
histogramFilter = [CIFilter filterWithName:@"CIAreaHistogram" keysAndValues:@"inputScale", @1, @"inputCount", @256, nil];
CIVector *extent = [[CIVector alloc] initWithCGRect:img.extent];
[histogramFilter setValue:extent forKey:@"inputExtent"];
[histogramFilter setValue:img forKey:kCIInputImageKey];
CIImage *histImg = histogramFilter.outputImage;

int rowBytes = 256 * 4; // ARGB has 4 components.
__fp16 byteBuffer[rowBytes]; // Buffer to render into
rowBytes *= 2; //__fp16 is 2 bytes

EAGLContext *myEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
NSDictionary *options = @{ kCIContextWorkingColorSpace : [NSNull null] };
CIContext *ctx = [CIContext contextWithEAGLContext:myEAGLContext options:options];
[ctx render:histImg toBitmap:byteBuffer
   rowBytes:rowBytes
     bounds:[histImg extent]
     format:kCIFormatRGBAh
 colorSpace:nil];

for (int i = 0; i < 256; i++)
{
    const __fp16* pixel = &byteBuffer[i*4];

    NSLog(@"%d: %f, %f, %f, %f\n", i, (float)pixel[0], (float)pixel[1], (float)pixel[2], (float)pixel[3]);
}

I don't think this will work on the simulator, because the simulator doesn't support kCIFormatRGBAh.

Munguia answered 22/12, 2015 at 1:10 Comment(1)
I updated this answer to show the correct way to calculate the input extent. The method given in some of the other answers is incorrect and can give wrong results.Munguia
B
1

There is an undocumented method on CIAreaHistogram call outputData that may be useful.

Buckskin answered 17/5, 2016 at 0:11 Comment(0)
A
0

I struggled with this for a while myself, although on iOS. I wanted to get the histogram data from live video for my own visualization, but ran into the same problem as Justin Mrkva.

Even with CIHistogramDisplayFilter the output didn't look quite right to me (too flat?), plus CIAreaHistogram was consuming something like 25-30% CPU all on its own. This is before drawing to an EAGLContext, haven't even brought the data back from the GPU yet...

So I scrapped Core Image altogether and turned to vImage, which works perfectly. It's still CPU intensive, but for me surprisingly less so than CIAreaHistogram, and the output matches what I see in Photoshop. It's not ideal if you're trying stay entirely on the GPU, but depending on your application it may be a better choice (it's certainly more precise).

It doesn't exactly answer your question, but here's how to get the histogram for a CGImage using vImage. I'm doing basically the same thing except with CVPixelBuffer and locking the base address. The channel order is different but it shouldn't matter:

#import <Accelerate/Accelerate.h>

CFDataRef imageData = CGDataProviderCopyData(CGImageGetDataProvider(image));

vImage_Buffer vImageBuffer;
vImageBuffer.data = (UInt8 *)CFDataGetBytePtr(imageData);
vImageBuffer.width = CGImageGetWidth(image);
vImageBuffer.height = CGImageGetHeight(image);
vImageBuffer.rowBytes = CGImageGetBytesPerRow(image);

vImagePixelCount red[256];
vImagePixelCount green[256];
vImagePixelCount blue[256];
vImagePixelCount alpha[256];
vImagePixelCount *histogram[4] = { red, green, blue, alpha };

vImage_Error error = vImageHistogramCalculation_ARGB8888(&vImageBuffer, histogram, 0);

if (error != kvImageNoError)
    NSLog(@"vImage error: %ld", error);

CFRelease(imageData);

// histogram now contains unsigned long counts for each channel

I would still like to know how to make proper use of CIAreaHistogram on iOS!

Anthropomorphous answered 8/2, 2015 at 18:20 Comment(1)
Dear jstn, thanks a lot for your input. I ended up taking a different approach altogether. After manually coding a CPU intensive histogram measurement that works like I want, it turned out the histogram approach wasn't the correct solution in the large picture... Thanks for telling me about vImage as now I at least know how I can get a working histogram in the future - but I will still try to figure out how it could be done with CIAreaHistogram for the sake of it!Cluny
S
0

Here's how to do what you wanted to do originally; as it turns out, changing the format in the context is what gives you all the different results you were getting.

This sample code, by the way, uses an EAGLContext, which is supposedly faster, although, in my experience so far, it is not. Nevertheless, here it is:

    CIFilter* histogram = [CIFilter filterWithName:@"CIAreaHistogram"];
[histogram setValue:inputImage forKey:@"inputImage"];
[histogram setValue:[CIVector vectorWithX:0.0 Y:0.0 Z:self.inputImage.extent.size.width W:self.inputImage.extent.size.height] forKey:@"inputExtent"];
[histogram setValue:@256 forKey:@"inputCount"];
[histogram setValue:@1.0 forKey:@"inputScale"];
/*id histogramData = [histogram valueForKey:@"outputData"];
if (histogramData)
    NSLog(@"outputData: %@", histogramData);*/

@autoreleasepool {
    CIImage* histogramImage = [histogram valueForKey:@"outputImage"];
    int rowBytes = 256 * 4; // ARGB has 4 components
    uint8_t byteBuffer[rowBytes]; // Buffer to render into

    EAGLContext *myEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    NSDictionary *options = @{ kCIContextWorkingColorSpace : [NSNull null] };
    CIContext *ctx = [CIContext contextWithEAGLContext:myEAGLContext options:options];
    //CIContext* ctx = [[CIContext alloc] init];
    [ctx render:histogramImage toBitmap:byteBuffer
       rowBytes:rowBytes
         bounds:[histogramImage extent]
         format:kCIFormatRGBAf
     colorSpace:nil];

    for (int i = 0; i < 256; i++)
    {
        const uint8_t* pixel = &byteBuffer[i*4];
        printf("%u, %u, %u\n", pixel[0], pixel[1], pixel[2]);
    }
}
Schurman answered 29/4, 2015 at 1:54 Comment(3)
Shouldn't kCIFormatRGBAf be kCIFormatRGBA8, since you are copying the data into bytes not floats?Munguia
How would you code this? I'd like to try whatever you come up with. I believe that swapping one for other mandates changes to other properties. I'd like to try whatever you come up withSchurman
If you stick with kCIFormatRGBAf, you would want to change uint8_t to float and rowBytes:rowBytes to rowBytes:rowBytes*4. (You are requesting data of size float, but only giving it 1 byte.)Munguia
S
0

By the way, here's working (and accurate) code for getting values from the CIAreaAverage Core Image filter:

- (CIImage *)outputImage
    CGRect inputExtent = [self.inputImage extent];
    CIVector *extent = [CIVector vectorWithX:inputExtent.origin.x
                                           Y:inputExtent.origin.y
                                           Z:inputExtent.size.width
                                           W:inputExtent.size.height];
    CIImage* inputAverage = [CIFilter filterWithName:@"CIAreaAverage" keysAndValues:@"inputImage", self.inputImage, @"inputExtent", extent, nil].outputImage;

    //CIImage* inputAverage = [self.inputImage imageByApplyingFilter:@"CIAreaMinimum" withInputParameters:@{@"inputImage" : inputImage, @"inputExtent" : extent}];
    EAGLContext *myEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    NSDictionary *options = @{ kCIContextWorkingColorSpace : [NSNull null] };
    CIContext *myContext = [CIContext contextWithEAGLContext:myEAGLContext options:options];

    size_t rowBytes = 32 ; // ARGB has 4 components
    uint8_t byteBuffer[rowBytes]; // Buffer to render into

    [myContext render:inputAverage toBitmap:byteBuffer rowBytes:rowBytes bounds:[inputAverage extent] format:kCIFormatRGBA8 colorSpace:nil];

    const uint8_t* pixel = &byteBuffer[0];
    float red   = pixel[0] / 255.0;
    float green = pixel[1] / 255.0;
    float blue  = pixel[2] / 255.0;
    NSLog(@"%f, %f, %f\n", red, green, blue);


    return outputImage;

Sample output:

2015-05-23 15:58:10.854 CIFunHouse[2400:489896] BSXPCMessage received error for message: Connection interrupted
2015-05-23 15:58:15.470 CIFunHouse[2400:489913] 0.000000, 0.000000, 0.305882
2015-05-23 15:58:15.582 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:15.622 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:15.665 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:15.706 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:15.748 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:15.792 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:15.834 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:15.874 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:15.915 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:15.956 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:15.998 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:16.040 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:16.079 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.121 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.163 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.205 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.247 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:16.288 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.330 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.372 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.413 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:16.455 CIFunHouse[2400:489895] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.496 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:16.543 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:16.579 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:16.623 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:16.667 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:16.705 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:16.746 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:16.788 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:16.829 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:16.873 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:16.912 CIFunHouse[2400:489970] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:16.955 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:16.996 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.038 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.080 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.122 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.163 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.205 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.246 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.288 CIFunHouse[2400:489970] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.330 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.371 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.415 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.456 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.496 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.539 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.580 CIFunHouse[2400:489895] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:17.622 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.664 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.705 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.746 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.788 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.830 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.873 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.914 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:17.956 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:17.997 CIFunHouse[2400:489913] 0.501961, 0.941176, 0.831373
2015-05-23 15:58:18.038 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.080 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.122 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.163 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.206 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.248 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.289 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.330 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.372 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.413 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.459 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.497 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.538 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.580 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.622 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.663 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.706 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.747 CIFunHouse[2400:489895] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.789 CIFunHouse[2400:489895] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.830 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.868 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:18.907 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.949 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:18.990 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.044 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.091 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.134 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.173 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:19.213 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.254 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:19.298 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:19.337 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:19.377 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:19.415 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:19.455 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:19.498 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:19.540 CIFunHouse[2400:489913] 0.564706, 0.917647, 0.831373
2015-05-23 15:58:19.588 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:19.630 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.674 CIFunHouse[2400:489913] 0.501961, 0.380392, 0.898039
2015-05-23 15:58:19.719 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.763 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.806 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.851 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:19.902 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:19.960 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:20.016 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:20.073 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:20.139 CIFunHouse[2400:489913] 0.000000, 0.882353, 0.898039
2015-05-23 15:58:20.198 CIFunHouse[2400:489913] 0.000000, 0.882353, 0.898039
2015-05-23 15:58:20.253 CIFunHouse[2400:489913] 0.000000, 0.882353, 0.898039
2015-05-23 15:58:20.316 CIFunHouse[2400:489913] 0.313726, 0.894118, 0.831373
2015-05-23 15:58:20.389 CIFunHouse[2400:489913] 0.313726, 0.894118, 0.831373
2015-05-23 15:58:20.472 CIFunHouse[2400:489913] 0.313726, 0.894118, 0.831373
2015-05-23 15:58:20.554 CIFunHouse[2400:489913] 0.313726, 0.894118, 0.831373
2015-05-23 15:58:20.631 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:20.714 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:20.784 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:20.839 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:20.887 CIFunHouse[2400:489913] 0.690196, 0.309804, 0.819608
2015-05-23 15:58:20.935 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
2015-05-23 15:58:20.981 CIFunHouse[2400:489913] 0.752941, 0.858824, 0.890196
Schurman answered 23/5, 2015 at 23:3 Comment(2)
Why are you allocating 32 bytes when you are only interested in the first pixel (only 4 bytes)?Munguia
I'm just asking why you used 32 instead of 16.Munguia
O
0

Four key points:

  • Use floating point instead of integer based format.
  • Match the bitmap pointer with the format. e.g. CIFormat.RGBAf uses Float32, .RGBAh uses Float16.
  • Provide a working color space. The system will convert the image to the working color space and calculate the histogram on this color space. You can also specify an NSNull() which means using the original color values without conversion.
  • Can't use an arbitrary render color space. I suggest using nil.

Code:

import Foundation
import CoreImage
import CoreImage.CIFilterBuiltins

struct Histogram {
    let bucketCount: Int

    typealias Component = Float32
    private var colorData: [Component]

    init(image: CIImage, extent: CGRect, bucketCount: Int, workingColorSpace: CGColorSpace?) {
        self.bucketCount = bucketCount
        self.colorData = .init(repeating: 0.0, count: bucketCount * 4)

        let filter = CIFilter.areaHistogram()
        filter.inputImage = image
        filter.extent = extent
        filter.count = bucketCount

        let context = if let workingColorSpace {
            CIContext(options: [.workingColorSpace : workingColorSpace])
        } else {
            CIContext(options: [.workingColorSpace: NSNull()])
        }

        let output = filter.outputImage ?? CIImage()

        context.render(output,
                       toBitmap: &colorData,
                       rowBytes: bucketCount * 4 * MemoryLayout<Component>.stride,
                       bounds: output.extent,
                       format: .RGBAf,
                       colorSpace: nil)
    }

    func value(at index: Int, colorComponent: Int) -> Double {
        assert(0 <= index && index < bucketCount && 0 <= colorComponent && colorComponent < 4)
        return Double(colorData[index * 4 + colorComponent])
    }
}
Officialism answered 5/6, 2024 at 9:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.