3

I'm trying to write an RBGA pixel array to file using code found here [stackoverflow.com], but it fails.

I store my pixel information in a 1-dimensional array of 32-bit integers, so the first bit of this method (hopefully) takes the relevant bits from the 32-bit integer, storing them into an array of chars.

When run, the method below a) prints that we didn't create the image (CGBitmapContextCreateImage(bitmapContext) returned NULL) and b) dies when trying to release the image (RayTracerProject[33126] <Error>: CGBitmapContextCreateImage: invalid context 0x0 ImageIO: <ERROR> CGImageDestinationAddImage image parameter is nil), which makes sense because cgImage is null.

The method:

-(void) write{

char* rgba = (char*)malloc(width*height);
for(int i=0; i < width*height; ++i) {
    rgba[4*i] = (char)[Image red:pixels[i]];
    rgba[4*i+1] = (char)[Image green:pixels[i]];
    rgba[4*i+2] = (char)[Image blue:pixels[i]];
    rgba[4*i+3] = (char)[Image alpha:pixels[i]];
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate(
                                                   rgba,
                                                   width,
                                                   height,
                                                   8, // bitsPerComponent
                                                   width, // bytesPerRow
                                                   colorSpace,
                                                   kCGImageAlphaNoneSkipLast);

CFRelease(colorSpace);

CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);

if ( cgImage == NULL ) 
    printf("Couldn't create cgImage correctly"). 

CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("image.png"), kCFURLPOSIXPathStyle, false);

CFStringRef type = kUTTypePNG; // or kUTTypeBMP if you like
CGImageDestinationRef dest = CGImageDestinationCreateWithURL(url, type, 1, 0);

CGImageDestinationAddImage(dest, cgImage, 0);

CFRelease(cgImage);
CFRelease(bitmapContext);
CGImageDestinationFinalize(dest);
free(rgba);
}; 

For completeness, my bit shifting methods:

// Extract the 8-bit red component from a 32bit color integer. 
+(int) red:(int) color{
 return (color >> 16) & 0xff;
};

// Extract the 8-bit green component from a 32bit color integer. 
+(int) green:(int) color{
  return (color >> 8) & 0xff;
};

// Extract the 8-bit blue component from a 32bit color integer. 
+(int) blue:(int) color{
  return (color & 0xff);
};

// Extract the 8-bit alpha component from a 32bit color integer. 
+(int) alpha:(int) color{
  return (color >> 24) & 0xff;
};

According to the docs found here [developer.apple.com], CGBitmapContextCreateImage will return A new bitmap context, or NULL if a context could not be created, but won't help me understand why a context couldn't be created.

What am I doing wrong? [Also, if there is a much smarter way to write a pixel array to file in Objective-C, I'm not tied to this particular way of doing things - this project is a port from a Java one I did a few years ago - I used a FileOutputStream, but can't find an equivalent in Objective-C].

Cœur
  • 37,241
  • 25
  • 195
  • 267
simont
  • 68,704
  • 18
  • 117
  • 136

1 Answers1

3

I have it working now - posting this in case somebody else finds this useful one day.

There are a few bugs with the code above - I didn't use width*4 for the bytes per row in the call to CGBitmapContextCreate, for example, and my rgba array was 4x smaller then it should have been. The code below runs, although I don't know where the image is saved to (I'm going to hardcode a path until I figure it out).

-(void) write{

 //printf("First pixel is: %d\n", pixels[0]);
 // RGBA array needs to be 4x pixel array - using 4bytes per int.  
 char* rgba = (char*)malloc(width*height*4);
 //printf("Size of char array: %d\n", width*height*4);

 for(int i=0; i < width*height; ++i) {
    rgba[4*i] = (char)[Image red:pixels[i]];
    rgba[4*i+1] = (char)[Image green:pixels[i]];
    rgba[4*i+2] = (char)[Image blue:pixels[i]];
    rgba[4*i+3] = (char)[Image alpha:pixels[i]];
 }
 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 CGContextRef bitmapContext = CGBitmapContextCreate(
                                                   rgba,
                                                   width,
                                                   height,
                                                   8, // bitsPerComponent
                                                   width*4, // bytesPerRow (4x larger then width)
                                                   colorSpace,
                                                   kCGImageAlphaNoneSkipLast);

 CFRelease(colorSpace);

 CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);

 /*if ( cgImage == NULL ) 
      printf("Couldn't create cgImage correctly");
  else
      printf("Has been created correctly");
 */
 CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,  CFSTR("image.png"), kCFURLPOSIXPathStyle, false);

 CFStringRef type = kUTTypePNG; // or kUTTypeBMP if you like
 CGImageDestinationRef dest = CGImageDestinationCreateWithURL(url, type, 1, 0);

 CGImageDestinationAddImage(dest, cgImage, 0);

 CFRelease(cgImage);
 CFRelease(bitmapContext);
 CGImageDestinationFinalize(dest);
 free(rgba);
}; 
simont
  • 68,704
  • 18
  • 117
  • 136
  • Don't forget to `free(rgba)`. This can be done after `CGBitmapContextCreateImage` as that is a copy operation. – Demitri Dec 22 '12 at 16:58