Home

Awesome

MPWDrawingContext, version 0.3

An Objective-C wrapper around the CoreGraphics CGContextRef. It includes the MPWDrawingContext protocol and the MPWCGDrawingContext class that implements that protocol by calling CoreGraphics CGContextRef functions.

The idea is for the context to be lightweight and straightforward enough that including it is a no-brainer.

The MPWDrawingContext protocol itself has no dependencies on AppKit, UIKit or CoreGraphics.

Also includes is MPWView, a View class that subclasses UIView on iOS and NSView on OSX and renders using an MPWDrawingContext. The sample applications for both OSX and iOS use the same view code. MPWView also allows drawing and event-handling code to be specified using blocks, so trivial views don't require a subclass.

PhoneGeometry.h provides NSPoint, NSSize and NSRect data-types on iOS by mapping them to their CoreGraphcis equivalents there.

The protocol aims to provide a "fluent" interface so that commands can be chained.

ChangeLog

Version 0.3

Version 0.2

Version 0.1

Future Plans

Creation

Creation methods are not part of the MPWDrawingContext protocol, but left to specific context classes.

If you already have a CGContextRef or want to use the current one:

+contextWithCGContext:(CGContextRef)c;
-initWithCGContext:(CGContextRef)newContext;
+currentContext;

Alternatively you can create a new bitmap context with a current size.

+rgbBitmapContext:(NSSize)size;
+cmykBitmapContext:(NSSize)size;

-initBitmapContextWithSize:(NSSize)size colorSpace:(CGColorSpaceRef)colorspace;

Paths

These are direct analogs to the corresponding CG functions. Use like this: [[context nsrect:[self bounds]] fill];

Construction:

-(id <MPWDrawingContext>)moveto:(float)x :(float)y;
-(id <MPWDrawingContext>)lineto:(float)x :(float)y;
-(id <MPWDrawingContext>)curveto:(float)cp1x :(float)cp1y :(float)cp2x :(float)cp2y :(float)x :(float)y;
-(id <MPWDrawingContext>)closepath;
-(id <MPWDrawingContext>)nsrect:(NSRect)r;
-(id <MPWDrawingContext>)arcWithCenter:(NSPoint)center radius:(float)radius startDegrees:(float)start endDegrees:(float)stop  clockwise:(BOOL)clockwise;
-(id <MPWDrawingContext>)arcFromPoint:(NSPoint)p1 toPoint:(NSPoint)p2 radius:(float)radius;
-(id <MPWDrawingContext>)ellipseInRect:(NSRect)r;

Drawing:

-(void)clip;
-(void)fill;
-(void)eofill;
-(void)eofillAndStroke;
-(void)fillAndStroke;
-(void)stroke;

Gradients:

-(id <MPWDrawingContext>)drawLinearGradientFrom:(NSPoint)startPoint to:(NSPoint)endPoint colors:(NSArray*)colors offsets:(NSArray*)offsets;
-(id <MPWDrawingContext>)drawRadialGradientFrom:(NSPoint)startPoint radius:(float)startRadius to:(NSPoint)endPoint radius:(float)endRadius colors:(NSArray*)colors offsets:(NSArray*)offsets;

Images

There is currently just a single method:

-(id <MPWDrawingContext>)drawImage:anImage;

The type of the image is purposely undefined. MPWCGDrawingContext expects that it either responds to (a) -CGImage to return a CGImageRef (as both UIImage and NSBitmapImageRep do) or (b) can render itself using -drawOnContext:(id <MPWDrawingContext>)aContext;

Images are rendered at their natural size (sent -size).

Text

-(id <MPWDrawingContext>)show:(id)someText; 
-(id <MPWDrawingContext>)setTextPosition:(NSPoint)p;
-(id)fontWithName:(NSString*)name size:(float)size;
-(id <MPWDrawingContext>)setFont:aFont;

Text support is currently very rudimentary, sporting only the single -show: message. It can be passed either an NSString or an NSAttributedString. In the former case, drawing parameters will be taken from the current graphics state, in the latter they will be taken from the NSAttributedString itself.

The -setFont: message takes as its argument a font returned by -fontWithName:size:. We purposely don't specify what exact class the font will be.

Graphics State

Setting colors uses the same pattern as setting fonts: there are two basic color-setting messages:

-(id <MPWDrawingContext>)setFillColor:(id)aColor;
-(id <MPWDrawingContext>)setStrokeColor:(id)aColor;

These are complemented by a number of messages returning color objects, again we don't exactly say what class they will be, just that they are compatible with the color-setting methods.

-(id)colorRed:(float)r green:(float)g blue:(float)b alpha:(float)alpha;
-(id)colorCyan:(float)c magenta:(float)m yellow:(float)y black:(float)k alpha:(float)alpha;
-(id)colorGray:(float)gray alpha:(float)alpha;

So setting an RGB fill color works as follows:

[context setFillColor:[context colorRed:1.0 green:0 blue:0 alpha:0]];

There are also plural versions of these methods:

-(id)colorsRed:r green:g blue:b alpha:alpha;
-(id)colorsCyan:c magenta:m yellow:y black:k alpha:alpha;
-(id)colorsGray:gray alpha:alpha;

The key to these methods is that they allow array parameters and allow array an non-array parameters to be mixed. For example:

[context colorRed:@[ 0.0 0.5 1.0 ] green:@0 blue:@0 alpha:@1];

will return 3 colors ranging from black to red. This comes in handy when specifying sets of related colors, for example for gradients.

CTM transformations:

-(id <MPWDrawingContext>)translate:(float)x :(float)y;
-(id <MPWDrawingContext>)scale:(float)x :(float)y;
-(id <MPWDrawingContext>)rotate:(float)degrees;

Saving and restoring:

-(id <MPWDrawingContext>)gsave;
-(id <MPWDrawingContext>)grestore;

Miscellaneous parameters:

-(id <MPWDrawingContext>)setdashpattern:array phase:(float)phase;
-(id <MPWDrawingContext>)setlinewidth:(float)width;
-(id <MPWDrawingContext>)setlinecapRound;
-(id <MPWDrawingContext>)setlinecapSquare;
-(id <MPWDrawingContext>)setlinecapButt;


-(id <MPWDrawingContext>)setAlpha:(float)alpha;
-(id <MPWDrawingContext>)setAntialias:(BOOL)doAntialiasing;
-(id <MPWDrawingContext>)setShadowOffset:(NSSize)offset blur:(float)blur color:(id)aColor;
-(id <MPWDrawingContext>)clearShadow;

Block-based:

-withShadowOffset:(NSSize)offset blur:(float)blur color:aColor draw:(DrawingBlock)commands;
-ingsave:(DrawingBlock)drawingCommands;
-(id)drawLater:(DrawingBlock)drawingCommands;
-layerWithSize:(NSSize)size content:(DrawingBlock)drawingCommands;
-laterWithSize:(NSSize)size content:(DrawingBlock)drawingCommands;
-page:(NSDictionary*)parameters content:(DrawingBlock)drawingCommands;

Convenience:

-(id <MPWDrawingContext>)translate:(id)aPoint;
-(id <MPWDrawingContext>)scale:(id)aPointOrNumber;
-(id <MPWDrawingContext>)moveto:(id)aPoint;
-(id <MPWDrawingContext>)lineto:(id)aPoint;

Infrequently Asked Questions

Why would anyone need an Objective-C drawing context?

In short, while CoreGraphics is an awesome graphics subsystem, not having OO features makes CGContext closed to extension by anyone but Apple, and somewhat unpleasant to use, IMHO.

I explain a bit more about the motivation on my blog: http://blog.metaobject.com/2012/06/pleasant-objective-c-drawing-context.html

Who cares about possible future expansion when that means there's lots of code to integrate with nasty dependencies?

It used to be just 1 Class, 1 Protocol, 3 extra include files to equalize some of the differences between iOS and OSX (could probably be reduced), but it's admittedly a little more now: Classes for different context types, for storing drawing commands and even (gasp!) an abstract class that captures some commonality between contexts. Still totally worth it, though.

1 additional class (MPWView) is purely optional

In the github project, the code is actually integrated into an adapted version of Matt Gallagher's IconApp (with some inspiration from Marcus Crafter's iOS port of same), so you have a working example right there.

But Cocoa has some fine drawing functionality with NSBezierPath, NSAffineTransform and friends

True, but MPWDrawingContext works identically on both iOS and Mac OS X. In fact there's also an MPWView class that works on both iOS and OSX, which is used in the sample code mentioned above to create an iOS app using the same drawing code as the OS X app.

I also prefer my graphics context to not be a hidden global parameter that's implicitly used by a bunch of other objects.

Who cares about OSX^H^H^H iOS?

Based on my unscientific experiments, MPWDrawingContext reduces the code I have to write for even one of the two platforms by about 20-30%. Your mileage will almost certainly vary.

Who cares about less code?

Well, it\s not just less code, it's more pleasant code as well:

[[[[[context moveto:0 :0] lineto:100 :0] lineto:50 :50] closepath] stroke];

vs.

CGContextMoveToPoint( context, 0, 0 );
CGContextAddLineToPoint( context, 100, 0);
CGContextAddLineToPoint( context, 50, 50 );
CGContextClosePath( context );
CGContextFillPath( context );

And

    bitmap = [context bitmapWithSize:NSMakeSize( 595, 842 ) commands:^(Drawable){  ... }];

vs.

bitmapContext = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0,
                       CGColorSpaceCreateDeviceRGB(),   
                       kCGImageAlphaPremultipliedLast)  | kCGBitmapByteOrderDefault );
{ .... } 
    cgBitmap = CGBitmapContextCreateImage( bitmapContext );
bitmap = [UIImage imageWithCGImage:cgBitmap];
CGImageRelease(cgBitmap);
CGContextRelease(bitmapContext);

No it's not!

OK :-)