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?
Three suggestions:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With