Procedural buttons using CoreGraphics...

Just wanted to share my success. Due in no small part to several posts here (plus the excellent Alan Quartermain blog) I was able to create a class that, when called, creates an ofImage of a procedural button done with CoreGraphics, with t3h shiney, text, shadows and all.

[attachment=0:1xngw86r]Screenshot 2009.10.18 10.43.25.png[/attachment:1xngw86r]

All you do is define ofImage myButton, then call this makeButton class with the width, height, and text. I’m currently using alpha to draw the button’s inactive state, but it would be easy to do a pair with different states, should the desire arise. I’m going to clean up the code and post it when time permits, as I imagine this would be handy for a lot of people, and doing CoreGraphics drawing within an openFrameworks iPhone project is quite a pain.

Chris Randall
Audio Damage, Inc.

![]( 2009.10.18 10.43.25.png)

that’s so cool
I’m using my own engine to create button, text,etc with png files, but Procedural button is really something well done. Looking forward for ur code.

Okay, here’s the code you’ll need to make this. Note that it has some size limitations, and the font you specify must be one of the iPhone system fonts.

void testApp::makeButton(int width, int height, const char *text, ofImage &image)  
	// Set up the colorspace and what-not  
	CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();  
	CGContextRef context = CGBitmapContextCreate(nil, width, height, 8, 0, colorSpace, kCGImageAlphaPremultipliedFirst);  
	CGImageRef theCGImage;  
	CGContextSetLineCap(context, kCGLineCapRound);  
	CGContextSetShouldAntialias(context, true);  
	CGContextSetRGBFillColor(context, 0.5,0.5,0.5,1);  
	CGContextSetRGBStrokeColor(context, 1, 1, 1, 1);  
	// We need to inset a bit to allow for the border to be stroked.   
	CGRect bounds = CGRectMake(2,2, width - 4, height - 4);  
	//This can be anything up to height / 2, of course. I think 12 looks nice.   
        float cornerRadius = 12.0;  
	CGMutablePathRef _path = CGPathCreateMutable();  
	CGPathMoveToPoint( _path, NULL, CGRectGetMinX(bounds),   
					  CGRectGetMinY(bounds) + cornerRadius );  
	CGPathAddLineToPoint( _path, NULL, CGRectGetMinX(bounds),   
						 CGRectGetMaxY(bounds) - cornerRadius );  
	CGPathAddArcToPoint( _path, NULL, CGRectGetMinX(bounds), CGRectGetMaxY(bounds),  
						CGRectGetMinX(bounds) + cornerRadius,   
						CGRectGetMaxY(bounds), cornerRadius );  
	CGPathAddLineToPoint( _path, NULL, CGRectGetMaxX(bounds)  - cornerRadius,   
						 CGRectGetMaxY(bounds) );  
	CGPathAddArcToPoint( _path, NULL, CGRectGetMaxX(bounds), CGRectGetMaxY(bounds),  
						CGRectGetMaxY(bounds) - cornerRadius, cornerRadius );  
	CGPathAddLineToPoint( _path, NULL, CGRectGetMaxX(bounds),   
						 CGRectGetMaxY(bounds) - cornerRadius );  
	CGPathAddArcToPoint( _path, NULL, CGRectGetMaxX(bounds), CGRectGetMinY(bounds),  
						CGRectGetMaxX(bounds) - cornerRadius,   
						CGRectGetMinY(bounds), cornerRadius );  
	CGPathAddLineToPoint( _path, NULL, CGRectGetMinX(bounds) + cornerRadius,   
	CGPathAddArcToPoint( _path, NULL, CGRectGetMinX(bounds), CGRectGetMinY(bounds),  
						CGRectGetMinY(bounds) + cornerRadius, cornerRadius );  
	CGPathCloseSubpath( _path );  
	CGContextSetLineWidth( context, 3.0 );  
	CGContextAddPath( context, _path );  
        // Here's where we clip so the gradient can draw with rounded corners  
        CGContextClip( context );  
	CGGradientRef myGradient;  
	CGColorSpaceRef myColorspace;  
	size_t num_locations = 2;  
	CGFloat locations[2] = { 0.0, 0.5 };  
	CGFloat components[8] = {  0.92, 0.92, 0.92, 1.0, 0.0, 0.0, 0.0, 0.9 };  
	myColorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);  
	myGradient = CGGradientCreateWithColorComponents (myColorspace, components, locations, num_locations);  
        // Make some shadows for the text and the border  
        CGSize shadowOffset = CGSizeMake(1.0, -1.0);  
	CGContextSetShadow(context, shadowOffset, 0.8);  
	CGContextDrawLinearGradient (context, myGradient, CGPointMake(bounds.origin.x, CGRectGetMaxY(bounds)),  
								 bounds.origin, 0);  
	CGContextAddPath( context, _path );  
	CGContextSetRGBStrokeColor(context, 1,1,1,1);  
        // Any iPhone system font can be used. "Zapfino" is fun.   
	CGContextSelectFont(context, "Arial Rounded MT Bold", 13.0, kCGEncodingMacRoman);  
	CGContextSetTextDrawingMode(context, kCGTextFill);  
	CGContextSetRGBStrokeColor(context, 1,1,1,1);  
	CGContextSetRGBFillColor(context, 1,1,1,1);  
	//This following bit is to get the width of the text so we can center it in the button  
        CGPoint pt;  
	float textX = CGRectGetMaxX(bounds) / 2.0;  
	float textY = CGRectGetMaxY(bounds) / 2.0;  
	CGContextSetTextDrawingMode(context, kCGTextInvisible);  
	CGContextShowTextAtPoint(context, 0, 0, text, strlen(text));  
	pt = CGContextGetTextPosition(context);  
	CGContextSetTextDrawingMode(context, kCGTextFill);  
	// Note that the textY + 1 is dependent on font size. Touch it up as needed.  
	CGContextShowTextAtPoint(context, (textX - pt.x / 2) + 2, textY + 1, text, strlen(text));  
	// Create a CG bitmap of the drawing rect  
        theCGImage = CGBitmapContextCreateImage(context);  
        // Uses an altered version of Zack's converter to use a CG image instead of a UI image.  
        iPhoneUIImageToOFImage(theCGImage, image);  
	CGContextClearRect(context, bounds);  

I didn’t make this in to a stand-alone class because I like to do the colors, sizes, what-not application-specific. Throw the following in testApp’s public: in the .h:

void makeButton(int width, int height, const char *text, ofImage &image);  

And finally, here’s the altered version of Zack’s UIconverter thingie. I just changed one line so that it was starting with the CGimage, rather than converting a UIimage. I just inlined it; if you want to do that, throw the following in testApp.h’s public as well:

void iPhoneUIImageToOFImage(CGImage *uiImage, ofImage &outImage) {  
		if(!uiImage) return;  
		CGImageRef cgImage;  
		CGContextRef spriteContext;  
		GLubyte *pixels;  
		size_t	width, height;  
		int bpp = 4;  
		// Creates a Core Graphics image from an image file  
		cgImage = uiImage;  
		// Get the width and height of the image  
		width = CGImageGetWidth(cgImage);  
		height = CGImageGetHeight(cgImage);  
		// Allocated memory needed for the bitmap context  
		pixels = (GLubyte *) malloc(width * height * bpp);  
		// Uses the bitmatp creation function provided by the Core Graphics framework.   
		spriteContext = CGBitmapContextCreate(pixels, width, height, 8, width * 4, CGImageGetColorSpace(cgImage), kCGImageAlphaPremultipliedLast);  
		// After you create the context, you can draw the sprite image to the context.  
		CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), cgImage);  
		// You don't need the context at this point, so you need to release it to avoid memory leaks.  
		// vertically flip  
		GLubyte *pixelsFlipped = (GLubyte *) malloc(width * height * bpp);  
		int numBytesPerRow = width * bpp;  
		for(int y=0; y<height; y++) {  
			memcpy(pixelsFlipped + (numBytesPerRow * y), pixels + (numBytesPerRow * (height - 1 - y)), numBytesPerRow);  
		outImage.setFromPixels(pixelsFlipped, width, height, OF_IMAGE_COLOR_ALPHA, true);  

Now all that is put up with the caveat that I’m no Master Coder by any stretch of the imagination. There are almost certainly better ways to do all of that, and I’d be pleased if someone could figure a fancier way out.

Anyhow, with the above code, just make ofImage myButton; and then in your setup say:

makeButton(width, height, "Howdy!", myButton);  

Then you can fling it around like any other ofImage. The result of the above code, as you see it, is making these buttons (obviously, they look much better on the iPhone than in this 72dpi screenshot):

[attachment=0:2zoy9pmz]Screenshot 2009.10.21 12.29.11.png[/attachment:2zoy9pmz]

Chris Randall

![]( 2009.10.21 12.29.11.png)

That looks awesome!

One question though; how do you go about linking these buttons to touch events? I’m still an OF beginner, but it seems that all events happen on the base canvas, so figuring out what should happen on user input can become a bit of a hassle I imagine :slight_smile: