MPWDrawingContext
and adding more and more graphics (for example,
I now do my icons in code), I've discovered a lot about making drawing with code a more pleasant
experience.Blocks
Blocks seem to be a really wonderful match for a graphics context, and most of the changes involve blocks in some way.Bracketing operations such as gsave/grestore now have block versions, so the Objective-C block structure reflects the nesting:
[context ingsave:^(Drawable c ){ [c translate:@[ @130 ,@140]]; [c setFont:[context fontWithName:@"ArialMT" size:345]]; [c setTextPosition:NSMakePoint(0, 0)]; [c show:@"\u2766"]; }];This is somewhat more compact than the plain code, which for correctness should also have a @try/@finally block wrapped around the basic drawing so exceptions don't mess up the graphics state stack.
[context gsave]; [context translate:@[ @130 ,@140]]; [context setFont:[context fontWithName:@"ArialMT" size:345]]; [context setTextPosition:NSMakePoint(0, 0)]; [context show:@"\u2766"]; [context grestore];Similar for drawing shadows:
[context withShadowOffset:NSMakeSize(0, -8 * scale) blur:12 * scale color:[context colorGray:0 alpha: 0.75] draw:^(Drawable c ){ [[[c setFillColorGray:0.9 alpha:1.0] ellipseInRect:ellipseRect] fill]; }];Again, this seems a little clearer than having to explicitly set and unset, makes it harder to miss the end of the bracket when moving code around and remains exception-safe.
[context sethadowOffset:NSMakeSize(0, -8 * scale) blur:12 * scale color:[context colorGray:0 alpha: 0.75]]; [[[context setFillColorGray:0.9 alpha:1.0] ellipseInRect:ellipseRect] fill]; [context clearShadow];
Stored, delayed and repeated drawing
You can create an object for later drawing by sending the-laterWithSize:(NSSize)size content:(DrawingBlock)commands
message. For example, here is a simple diamond shape:
NSSize diamondSize=NSMakeSize(16,16); id diamond = [context laterWithSize:diamondSize content:^(id context){ id red = [context colorRed:1.0 green:0.0 blue:0.0 alpha:1.0]; [context setFillColor:red]; [[context moveto:diamondSize.width/2 :2] lineto:diamondSize.width-2 :diamondSize.height/2]; [[context lineto:diamondSize.width/2 :diamondSize.height-2] lineto:2 :diamondSize.height/2]; [[context closepath] fill]; }];We can now draw this anywhere we want, and at any scale or orientation, using the
-drawImage:
message.
[context drawImage:diamond];You also have
layerWitSize:content:
and bitmapWithSize:content:
messages if you want to specifically use
CGLayer
or CGImage
instead, but using laterWithSize:content:
preserves maximum quality, and it
will automatically switch to a CGLayer
when rendering to a PDF context in order to minimize
PDF file size.Patterns
I talked about patterns earlier. What I didn't mention then was that this is just the ability to use a stored set of drawing commands (see previous section) as a color:[context setColor:diamond];I am not going to post the comparison to plain CG here, you can read it in the original Apple documentation.
I should note that this currently works for colored patterns, not for uncolored patterns, due to the fact that I haven't yet exposed color spaces. The basic process will be very similar.
Polymorphic object arguments
Path construction and graphics state messages with point arguments are now available in a version that takes a single object argument, in addition to the format with anonymous float arguments (moveto:(float)x :(float)y
).
- moveto:
- lineto:
- translate:
- scale:
[context moveto:@[ @10, @20]];Alternatively, any custom object that responds to
count
and either objectAtIndex:
, (float)realAtIndex:
or getReals:(float*)buffer length:(int)maxLen
can be used. The scale:
message can also take a single NSNumber
and will treat that as uniform x and y scale.
Linecap parameters
Linecap parameters can now be set using distinct message:- setlinecapRound
- setlinecapButt
- setlinecapSquare
[context setlinecapRound];vs.
[context setLinecap: kCGContextLinecapRound];
Future
Another reason to be as purely message-based as possible is that it makes bridging to other languages easier, for example for interactive drawing environments: Creating a badge (youtube).
I've also started experimenting with other outputs, for example creating a version of the same badge
composed of CALayer
objects using the same drawing commands. Other output should follow, for
example web with SVG or HTML 5 Canvas or direct OpenGL textures.
I also want to finally add image processing operations both stand-alone and as chained drawing contexts, as well as getting more complex text layout options in there.
p.s.: now on hacker news