How to render objects into an image

For Kabaam, Holger needed to render Sparrow objects into a JPG file.

In order to accomplish that the whole display tree has to be rendered to an exportable image. That's why he extended the SPRenderTexture to allow rendering it to an UIImage.

@interface SPRenderTexture (RenderToImage)
  - (UIImage*)renderToImage;
@end

And here is the magical part. In short, it makes use of the bundleDrawCalls method on the SPRenderTexture to get to the current GL context. It then allocates memory for the resulting image and reads the pixels from the GL context into the allocated array. After that we can feed that array to a CGDataProvider, a CGImageRef and then to an UIImage. All that’s left to do is tidy up some memory, which is unfortuantely very C-ish in this case: We have to register a releaseRawImageDataBufferCallback with the CGDataProvider in order to release the allocated array when everything is over. Check it out:

@implementation SPRenderTexture (RenderToImage)
 
void releaseRawImageDataBufferCallback(void *info, const void *data, size_t size)
{
  free((void*)data);
}
 
- (UIImage*)renderToImage
{
  __block UIImage *image = nil;
 
  [self bundleDrawCalls:^() 
  {
    float scale = [SPStage contentScaleFactor];
    int width = scale * self.width;
    int height = scale * self.height;
    int nrOfColorComponents = 4; //RGBA
    int bitsPerColorComponent = 8;
    int rawImageDataLength = width * height * nrOfColorComponents;
    GLenum pixelDataFormat = GL_RGBA;
    GLenum pixelDataType = GL_UNSIGNED_BYTE;
    BOOL interpolateAndSmoothPixels = NO;
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
 
    CGDataProviderRef dataProviderRef;
    CGColorSpaceRef colorSpaceRef;
    CGImageRef imageRef;
 
    @try 
    {
      GLubyte *rawImageDataBuffer = (GLubyte *) malloc(rawImageDataLength);
 
      glReadPixels(0, 0, width, height, pixelDataFormat, pixelDataType, rawImageDataBuffer);
 
      dataProviderRef = CGDataProviderCreateWithData(NULL, rawImageDataBuffer, rawImageDataLength, releaseRawImageDataBufferCallback);
      colorSpaceRef = CGColorSpaceCreateDeviceRGB();
      imageRef = CGImageCreate(width, height, bitsPerColorComponent, bitsPerColorComponent * nrOfColorComponents, width * nrOfColorComponents, colorSpaceRef, bitmapInfo, dataProviderRef, NULL, interpolateAndSmoothPixels, renderingIntent);
      image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
    }
    @finally 
    {
      CGDataProviderRelease(dataProviderRef);
      CGColorSpaceRelease(colorSpaceRef);
      CGImageRelease(imageRef);
    }
  }];
 
  return image;
}
 
@end

With this extension, you can render any object into a UIImage – you just have to render it into a normal SPRenderTexture and then call the “renderToImage” method on the render texture.