Example code programming Objective-C with Cocoa in Xcode and Interface Builder (Leopard)

32: Quartz drawing with Bitmap and Garbage Collection

Problem: We want to display a bitmap image using Quartz with Garbage Collection. However Quartz does not implement Gargage Collection but appears to use its own memory managament mechanism. Although we can use NSAllocateCollectable to provide our bitmap, we need to prevent Quartz from clearing it so we don't create problems for the Gargbage Colector. Then we must also keep pointers to Quartz routines from being cleared by the GC and ensure that the Quartz data structures are cleared once we have done with them. Unfortunately examples of how to do this have been well hidden.

Solution: Use Interface Builder to define a NSWindow and a custom NSView. Obtain the RGB colour space when the custom NSView awakeFromNib method is called by the system at program start. For this we use the recipe from page 159 of Programming with Quartz by Celphman and Laden.

In the drawRect method we call a bitmap creation method that uses NSAllocateCollectable; We next set its RGB values so it provides a picture made of a gradient of colours.

The Quartz drawing using a bitmap and garbage collection

Quartz drawing of a bitmap done in a Garbage Collection environment

Pass the bitmap to a CGDataProviderRef creation routine based on page 189 of the Quartz book. Notice that the CGDataProviderRef routine requires that one define the callback routine rgbReleaseRGBDataProvider(void *info, const void *data, size_t size) which causes the bitmap to be released once Quartz has done with it. This routine is here implemented as a dummy routine that does nothing, i.e. we do not release the bitmap data but leave that to the Garbage Collector to dispose of whenever.

The CGDataProviderRef and CGColourSpace are passed to a CGImageRef creation method. This uses the recipe on page 222 of the Quartz book.

The drawing has to be done in the current graphics context, i.e. that of our custom NSView. Get that context using [NSGraphicsContext currentContext] and use that to get the graphicsPort which is the CGContextRef.

Everything is now ready for the drawing process to take place. A small fiddly thing needs doing first : use NSRectToCGRect to convert the drawRect:(NSRect)rect to a CGRect. Now we can call CGContextDrawImage and have our bitmap displayed in all its glory.

Finish by releasing the NSImage. And that is it.


Create a new project in XCode: File->New Project->Cocoa Application
Call it: 032_CGContext_bitmap

Now create the class: MyNSView of type NSView.
Code it as shown below and save.

// MyNSView.h // Created by julius on 10/11/2008. #import <Cocoa/Cocoa.h> @interface MyNSView : NSView { NSRect nsRectFrame; CGColorSpaceRef cgColourSpaceRef; } - (CGColorSpaceRef) obtainTheRGBColorSpace; - (unsigned char *)createBitmapWithRect:(NSRect)pRect; - (CGDataProviderRef) createCGDataProviderRefQuartzP189WithBitmap: (unsigned char *)pBitmap forRect:(NSRect)pRect; - (CGImageRef) createCGImageQuartzPage222UsingDataProvider: (CGDataProviderRef)pDataProvider andColourSpace:(CGColorSpaceRef)pCgColorSpaceRefDisplay forRect:(NSRect)pRect; - (void) paintGradientBitmap:(unsigned char *)pBitmap ofSizeInBytes:(NSInteger)pSizeInBytes; @end // MyNSView.m #import "MyNSView.h" @implementation MyNSView - (id)initWithFrame:(NSRect)pFrame { if ( !(self = [super initWithFrame:pFrame]) ) { return self; } // end if nsRectFrame = pFrame; return self; } - (void)awakeFromNib { cgColourSpaceRef = [self obtainTheRGBColorSpace]; } // end awakeFromNib - (unsigned char *)createBitmapWithRect:(NSRect)pRect { // Create a kCGImageAlphaNone RGB bitmap: Quartz p 216 // format: 3 component, 24 bit data no alpha, no padding bytes // RGBRGBRGB... // 8 bits per component, bits per pixel = 24, three component colour space RGB // allocate memory for 24bit rgb image NSInteger zIntMemorySizeInBytes = pRect.size.width * pRect.size.height * 3; // 3 = rgb zIntMemorySizeInBytes *= sizeof(unsigned char); unsigned char * zUcharPtrToBitmapData = (unsigned char *)NSAllocateCollectable( zIntMemorySizeInBytes, NSScannedOption); if(zUcharPtrToBitmapData == NULL) { NSLog(@"ERROR *** createBitmapWithRect: zUcharPtrToBitmapData == NULL"); exit(1); } // end if return zUcharPtrToBitmapData; } // end createBitmapWithRect static void rgbReleaseRGBDataProvider(void *info, const void *data, size_t size) { NSLog(@"call made to rgbReleaseRGBRampDataProvider"); // free((char *)data); } // end rgbReleaseRGBDataProvider - (CGDataProviderRef) createCGDataProviderRefQuartzP189WithBitmap: (unsigned char *)pBitmap forRect:(NSRect)pRect { NSLog(@"MyDataProviderRGBBitmap inside createCGDataProviderRefQuartzP189"); size_t zImagedataSize = pRect.size.width * pRect.size.height * 3; CGDataProviderRef zCGDataProviderRef = CGDataProviderCreateWithData( NULL, pBitmap, zImagedataSize, rgbReleaseRGBDataProvider); NSLog(@"MyDataProviderRGBBitmap leaving createCGDataProviderRefQuartzP189"); return zCGDataProviderRef; } // end createCGDataProviderRefQuartzP189 - (CGImageRef) createCGImageQuartzPage222UsingDataProvider:(CGDataProviderRef)pDataProvider andColourSpace:(CGColorSpaceRef)pCgColorSpaceRefDisplay forRect:(NSRect)pRect { size_t zBitsPerComponent = 8; size_t zBitsPerPixel = 24; size_t zBytesPerRow = pRect.size.width * 3; bool zBoolShouldInterpolate = true; NSLog(@"MyDataProviderRGBBitmap inside createCGImageQuartzPage222UsingDataProvider"); CGImageRef zCGImageRef = CGImageCreate((size_t)pRect.size.width, (size_t)pRect.size.height, zBitsPerComponent, zBitsPerPixel, zBytesPerRow, pCgColorSpaceRefDisplay, kCGImageAlphaNone, pDataProvider, NULL, zBoolShouldInterpolate, kCGRenderingIntentDefault); NSLog(@"MyDataProviderRGBBitmap leaving createCGImageQuartzPage222UsingDataProvider"); if (zCGImageRef == NULL) { NSLog(@"MyDataProviderRGBBitmap *** ERROR *** createCGImageQuartzPage222UsingDataProvider"); exit(1); } return zCGImageRef; } // end createCGImageQuartzPage222UsingColourSpace - (void) paintGradientBitmap:(unsigned char *)pBitmap ofSizeInBytes:(NSInteger)pSizeInBytes { NSLog(@"paintGradientBitmap"); unsigned char * zPtr = pBitmap; NSInteger y; for (y = 0; y < nsRectFrame.size.height; y++) { NSInteger x; unsigned char zucharGreen = round((255.0 / (CGFloat)nsRectFrame.size.height) * (CGFloat)y); for (x = 0; x < nsRectFrame.size.width; x++) { unsigned char zucharBlue = round((255.0 / (CGFloat)nsRectFrame.size.width) * (CGFloat)x); NSInteger zOffset = ( (nsRectFrame.size.width * y) + x) * 3 ; *(zPtr + zOffset) = 255; *(zPtr + zOffset + 1) = zucharGreen; *(zPtr + zOffset + 2 ) = zucharBlue; } // end for x } // end for y } // end paintGradientBitmap - (CGColorSpaceRef) obtainTheRGBColorSpace { // pp 159 Programming with Quartz, Celphman and Laden CMProfileRef zGenericRGBProfile = NULL; OSStatus zOSStatusError = noErr; CMProfileLocation zLoc; // build up a profile location for ColorSync zLoc.locType = cmPathBasedProfile; //strcpy(zLoc.u.pathLoc.path, kGenericRGBProfilePathStr); // did not spend time making it work strcpy(zLoc.u.pathLoc.path, "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"); // Open the profile with ColourSync zOSStatusError = CMOpenProfile(&zGenericRGBProfile, &zLoc); if( !(zOSStatusError == noErr) ) { NSLog(@"MyPictureView *** ERROR *** getTheDisplayColorSpace CMOpenProfile"); exit(1); } // CGColorSpaceRef zCgColorSpaceRefDisplay = CGColorSpaceCreateWithPlatformColorSpace(zGenericRGBProfile); if(zCgColorSpaceRefDisplay == NULL) { NSLog(@"MyPictureView *** ERROR *** getTheDisplayColorSpace zCgColorSpaceRefDisplay"); exit(1); } // end if return zCgColorSpaceRefDisplay; } // end obtainTheRGBColorSpace - (void)drawRect:(NSRect)pRect { unsigned char * zBitmap = [self createBitmapWithRect:pRect]; // strong? No. NSInteger zIntMemorySizeInBytes = pRect.size.width * pRect.size.height * 3; [self paintGradientBitmap:zBitmap ofSizeInBytes:(NSInteger)zIntMemorySizeInBytes]; __strong CGDataProviderRef zCgDataProvider = [self createCGDataProviderRefQuartzP189WithBitmap:zBitmap forRect:pRect ]; __strong CGImageRef zCGImageRef = [self createCGImageQuartzPage222UsingDataProvider:zCgDataProvider andColourSpace:cgColourSpaceRef forRect:pRect]; __strong NSGraphicsContext * nsGraphicsContext = [NSGraphicsContext currentContext]; __strong CGContextRef zCgContextRef = (CGContextRef) [nsGraphicsContext graphicsPort]; CGRect zCgRect = NSRectToCGRect(pRect); CGContextDrawImage(zCgContextRef,zCgRect,zCGImageRef); CGImageRelease(zCGImageRef); } // end drawRect @end

The Custom NSView

The Custom NSView

Double click MainMenu.xib to bring up Interface Builder.

Customise the view as shown above.

Select the custom view and in the Identity pane of the Inspector set its class to MyNSView.

Save and Run.

The Quartz drawing using a bitmap and garbage collection

Quartz drawing of a bitmap done in a Garbage Collection environment

If you want to download the code

Click the Download Link to obtain 032_CGContext_bitmap.zip file of this whole OS X 10.5 Leopard program.

Download 032_CGContext_bitmap.zip (2.3 MB)

Please send me your comments

If you include your e-mail I may reply!  

Page last modified: 18:58 Sunday 12th. May 2013

Julius Guzy

Paintings & Drawings

  • painting after Leonardo Da Vinci's Battle of Anghiari