0

I'd like to do the following:

  1. Read RGB color values from a 24 bit PNG image
  2. Average the RGB values and store them into an array of Glubytes.

I have provided my function that I was hoping would perform these 2 steps. My function returns an array of Glubytes, however all elements have a value of 0. So im guessing im reading the image data incorrectly.

What am i going wrong in reading the image? (perhaps my format is incorrect).

Here is my function:

+ (GLubyte *) LoadPhotoAveragedIndexPNG:(UIImage *)image numPixelComponents:    (int)numComponents
{
// Load an image and return byte array.
CGImageRef textureImage = image.CGImage;
if (textureImage == nil)
{
    NSLog(@"LoadPhotoIndexPNG: Failed to load texture image");
    return nil;
}

NSInteger texWidth = CGImageGetWidth(textureImage);
NSInteger texHeight = CGImageGetHeight(textureImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

GLubyte *indexedData = (GLubyte *)malloc(texWidth * texHeight);
GLubyte *rawData = (GLubyte *)malloc(texWidth * texHeight * numComponents);

CGContextRef textureContext = CGBitmapContextCreate(
                                                    rawData,
                                                    texWidth,
                                                    texHeight,
                                                    8,
                                                    texWidth * numComponents,
                                                    colorSpace,
                                                    kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(textureContext,
                   CGRectMake(0.0, 0.0, (float)texWidth, (float)texHeight),
                   textureImage);
CGContextRelease(textureContext);

int rawDataLength = texWidth * texHeight * numComponents;
for (int i = 0, j = 0; i < rawDataLength; i += numComponents)
{
    GLubyte b = rawData[i];
    GLubyte g = rawData[i + 1];
    GLubyte r = rawData[i + 2];
    indexedData[j++] = (r + g + b) / 3;
}

return indexedData;
}

Here is the test image im loading (RGB colorspace in PNG format): enter image description here

AlvinfromDiaspar
  • 6,611
  • 13
  • 75
  • 140
  • Hope this link helps http://stackoverflow.com/questions/448125/how-to-get-pixel-data-from-a-uiimage-cocoa-touch-or-cgimage-core-graphics – Ram Apr 29 '13 at 09:52

2 Answers2

1

Do check with some logging if the parameters b,g and r are producing normal values in the last for loop. Where you made a mistake is indexedData[j++] = (r + g + b) / 3; those 3 parameters are sizeof 1 byte and you can not sum them up like that. Use a larger integer, typecast them and typecast the result back to array. (You are most likely getting overflow)

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • Hmmm. Very good point about the overflow. I will try and see if this works. – AlvinfromDiaspar Apr 30 '13 at 09:16
  • Hi, i changed it so that im typecasting to int, then device by 3, and then typecast it back to a byte. But now im getting the value 85 for all averaged pixels. Not sure what's going on. – AlvinfromDiaspar May 01 '13 at 10:34
  • Maybe it's coincidence, but 255/3 = 85. Odd thing is, most of the pixels in my test image is black! – AlvinfromDiaspar May 01 '13 at 10:35
  • It turned out that prior to reading the image, i was converting it into ARGB first. So i fixed it by reading 4 components, discarding the first byte, the alpha component. – AlvinfromDiaspar May 02 '13 at 04:54
1

Apart from your original problem there's a major problem here (maybe even related)

for (int i = 0, j = 0; i < rawDataLength; i += numComponents)
{
    GLubyte b = rawData[i];
    GLubyte g = rawData[i + 1];
    GLubyte r = rawData[i + 2];
    indexedData[j++] = (r + g + b) / 3;
}

Namely the expression

    (r + g + b)

This expression will be performed on GLubyte sized integer operations. If the sum of r+g+b is larger than the type GLubyte can hold it will overflow. Whenever you're processing data through intermediary variables (good style!) choose the variable types large enough to hold the largest value you can encounter. Another method was casting the expression like

    indexedData[j++] = ((uint16_t)r + (uint16_t)g + (uint16_t)b) / 3;

But that's cumbersome to read. Also if you're processing integers of a known size, use the types found in stdint.h. You know, that you're expecting 8 bits per channel. Also you can use the comma operator in the for increment clause

uint8_t *indexedData = (GLubyte *)malloc(texWidth * texHeight);

/* ... */

for (int i = 0, j = 0; i < rawDataLength; i += numComponents, j++)
{
    uint16_t b = rawData[i];
    uint16_t g = rawData[i + 1];
    uint16_t r = rawData[i + 2];
    indexedData[j] = (r + g + b) / 3;
}
datenwolf
  • 159,371
  • 13
  • 185
  • 298