Reputation: 13057
I'm trying to get the average UIColor of the center 16 pixels of a UIImage, however the resulting color is never close to being correct. What am I doing wrong here? The point parameter is correct, I have verified this. I also have another method that just returns the UIColor for the center pixel using this same point that works great.
+ (UIColor*)getAverageColorForImage:(UIImage *)image atLocation:(CGPoint)point
{
int radialSize = 2;
int xStartPoint = point.x - radialSize;
int yStartPoint = point.y - radialSize;
int xEndPoint = point.x + radialSize;
int yEndPoint = point.y + radialSize;
CGImageRef rawImageRef = [image CGImage];
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(rawImageRef));
const UInt8 *rawPixelData = CFDataGetBytePtr(data);
NSUInteger bytesPerRow = CGImageGetBytesPerRow(rawImageRef);
NSUInteger stride = CGImageGetBitsPerPixel(rawImageRef) / 8;
unsigned int red = 0;
unsigned int green = 0;
unsigned int blue = 0;
for(int row = yStartPoint; row < yEndPoint; row++)
{
const UInt8 *rowPtr = rawPixelData + bytesPerRow * row;
for(int column = xStartPoint; column < xEndPoint; column++)
{
red += rowPtr[0];
green += rowPtr[1];
blue += rowPtr[2];
rowPtr += stride;
}
}
CFRelease(data);
CGFloat f = 1.0f / (255.0f * (yEndPoint - yStartPoint) * (xEndPoint - xStartPoint));
return [UIColor colorWithRed:f * red green:f * green blue:f * blue alpha:1.0f];
}
Upvotes: 0
Views: 1516
Reputation: 11
This post is from quite a while ago, but for anyone running across this looking to do something similar, I believe I have found a solution. This is, in essence, what a Photoshop color sampler will do with the 3x3, 5x5, 11x11, etc option selected. Your mileage may vary, but I found this works for me.
It's not really cleaned up and may look a bit janky, but it works.
+ (UIColor*)getAverageColorForImage:(UIImage *)image atLocation:(CGPoint)point withRadius:(int)radialSize
{
int sampleSize = (radialSize * 2) + 1;
int pixelsToCount = sampleSize * sampleSize;
int xStartPoint = point.x - radialSize;
int yStartPoint = point.y - radialSize;
// Make sure we are not running off the edge of the image
if(xStartPoint < 0){
xStartPoint = 0;
}
else if (xStartPoint + sampleSize > image.size.width){
xStartPoint = image.size.width - sampleSize - 1;
}
if(yStartPoint < 0){
yStartPoint = 0;
}
else if (yStartPoint + sampleSize > image.size.height){
yStartPoint = image.size.height - sampleSize - 1;
}
// This is the representation of the pixels we would like to average
CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, CGRectMake(xStartPoint, yStartPoint, sampleSize, sampleSize));
// Get the image dimensions
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
// Set the color space to RGB
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Understand the bit length of the data
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = (NSUInteger)8;
// Set up a Common Graphics Context
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (int)((bytesPerRow * 0) + 0 * bytesPerPixel);
CGFloat cumulativeRed = 0.0;
CGFloat cumulativeGreen = 0.0;
CGFloat cumulativeBlue = 0.0;
for (int ii = 0 ; ii < pixelsToCount ; ++ii){
CGFloat red = (rawData[byteIndex] * 1.0) / 255.0;
CGFloat green = (rawData[byteIndex + 1] * 1.0) / 255.0;
CGFloat blue = (rawData[byteIndex + 2] * 1.0) / 255.0;
__unused CGFloat alpha = (rawData[byteIndex + 3] * 1.0) / 255.0;
cumulativeRed += red;
cumulativeGreen += green;
cumulativeBlue += blue;
byteIndex += 4;
}
CGFloat avgRed = (cumulativeRed / pixelsToCount);
CGFloat avgGreen = (cumulativeGreen / pixelsToCount);
CGFloat avgBlue = (cumulativeBlue / pixelsToCount);
free(rawData);
return [UIColor colorWithRed:avgRed green:avgGreen blue:avgBlue alpha:1.0];
}
Upvotes: 1
Reputation: 3327
You are only iterating the pixels from startPoint
to one less of endPoint
. Maybe using
for(int row = yStartPoint; row <= yEndPoint; row++)
// ...
for(int column = xStartPoint; column <= xEndPoint; column++)
will fix it?
Also you will have to adjust your rowPtr
to start at your xStartPoint
:
const UInt8 *rowPtr = rawPixelData + bytesPerRow * row + stride * xStartPoint;
Else you will be starting at x = 0
every row.
And your average should be calculated like so:
int numberOfPixels = (yEndPoint - yStartPoint) * (xEndPoint - xStartPoint);
red /= numberOfPixels;
green /= numberOfPixels;
blue /= numberOfPixels;
return [UIColor colorWithRed:red / 255.0 green:green / 255.0 blue:blue / 255.0 alpha:1.0f];
Upvotes: 1