Additional Objective-C Drawing Context Enhancements

It’s been over six months since I first shared my pleasant Objective-C drawing contextpublic, and I’ve been busy! While adapting my code to use MPWDrawingContext and expanding its graphical capabilities (like coding icons), I’ve learned a lot about making code-based drawing more enjoyable.

Blocks

Blocks and graphics contexts are a perfect match, and most updates center around blocks.

Operations like gsave/grestore now have block versions for better nesting reflection in Objective-C:

1
2
3
4
5
6
    [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 more concise than the standard code, which needs a @try/@finally block for exception safety in the graphics state stack:

1
2
3
4
5
6
    [context gsave];
    [context translate:@[ @130 ,@140]];
    [context setFont:[context fontWithName:@"ArialMT" size:345]];
    [context setTextPosition:NSMakePoint(0, 0)];
    [context show:@"\u2766"];
    [context grestore];

Similarly, drawing shadows is easier:

1
2
3
    [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];
    }];

This is cleaner than manual setting and unsetting, harder to mess up when rearranging code, and remains exception-safe:

1
2
3
    [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 prepare drawings for later use by sending the -laterWithSize:(NSSize)size content:(DrawingBlock)commands message. Here’s a diamond shape example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    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];
        }];

This diamond can be drawn anywhere, at any scale and orientation, using -drawImage::

1
    [context drawImage:diamond];

You can use layerWitSize:content: and bitmapWithSize:content: for CGLayer or CGImage output. However, laterWithSize:content: maximizes quality and automatically uses CGLayer for PDF contexts to minimize PDF file size.

Patterns

As mentioned in earlier, patterns use stored drawing commands (from the previous section) as colors:

1
    [context setColor:diamond];

For a comparison with plain CG, refer to Apple’s documentation.

Currently, only colored patterns are supported because color spaces aren’t exposed yet. The process for uncolored patterns will be similar.

Polymorphic Object Arguments

Path construction and graphics state messages now accept a single object argument for point values, in addition to separate float arguments (e.g., moveto:(float)x :(float)y).

These messages are:

  • moveto:
  • lineto:
  • translate:
  • scale:

The single argument can be an Objective-C array:

1
    [context moveto:@[ @10, @20]];

Or any custom object responding to count and objectAtIndex:, (float)realAtIndex:, or getReals:(float*)buffer length:(int)maxLen. The scale: message also accepts a single NSNumber for uniform x and y scaling.

Linecap Parameters

Dedicated messages simplify linecap settings:

  • setlinecapRound
  • setlinecapButt
  • setlinecapSquare

While multiple messages might seem unusual, it reduces naming complexity. These scoped names are easier to grasp than global constant strings or enums:

1
    [context setlinecapRound];

versus:

1
    [context setLinecap: kCGContextLinecapRound];

Future

Message-based design simplifies bridging to other languages, such as interactive drawing environments like Creating a badge (youtube).

Experiments with other outputs are underway. The same badge can be rendered with CALayer objects using the same drawing commands. Future outputs include SVG, HTML5 Canvas, and direct OpenGL textures.

Image processing operations, both standalone and chained, are planned, alongside advanced text layout options.

P.S.: Now available on hacker news.

Licensed under CC BY-NC-SA 4.0
Last updated on Jan 11, 2024 17:39 +0100