0

I have created a view and in my draw rect method I create paths depending on what a user does with sliders. Using standard colors , everything works and looks very nice. I am trying to follow a code snippet from apple that shows how to draw patterns into a rect at this link: Apple Drawing Guide

The example shows how to create a function callback with the pattern desired and then an additional method call to draw the rect. If I call the code as it is written from my rect it will draw my pattern as I would expect, however, I do not want to fill my rect , I want to fill a specified path in the rect. If I change the call in the drawing method from CGContextFillRect to CGContextFillPath, it doesn't work. I'm sure there is something I am overlooking to modify this code to get it to do what I want.

My callback pattern is a simple checkerboard:

code:

// Call Back function for Graphics Pattern

#define PATTERN_SIZE 10

void patternSpec(void *info , CGContextRef pContext){

    NSLog(@"patternSpec Callback Called");
    CGFloat subUnit = PATTERN_SIZE / 2;

    CGRect square1 = {{0,0}, {subUnit, subUnit}},
    square2 = {{subUnit, subUnit}, {subUnit, subUnit}},
    square3 = {{0 , subUnit}, {subUnit, subUnit}},
    square4 = {{subUnit , 0}, {subUnit, subUnit}};


    CGContextSetRGBFillColor(pContext, 1.0, 0.0, 0.0, 1.0 );
    CGContextFillRect(pContext, square1);

    CGContextSetRGBFillColor(pContext, 1.0, 0.0, 0.0, 1.0 );
    CGContextFillRect(pContext, square2);

    CGContextSetRGBFillColor(pContext, 0.0, 0.0, 0.0, 1.0 );
    CGContextFillRect(pContext, square3);

    CGContextSetRGBFillColor(pContext, 0.0, 0.0, 0.0, 1.0 );
    CGContextFillRect(pContext, square4);

}

// Method that draws the pattern

static void drawPattern (CGContextRef myContext)
{
    NSLog(@"drawPattern Called ");
    CGPatternRef    pattern;
    CGColorSpaceRef patternSpace;
    CGFloat         alpha = 1.0;
    //width, height;

    static const CGPatternCallbacks callbacks = {0, &patternSpec, NULL};

    CGContextSaveGState (myContext);
    patternSpace = CGColorSpaceCreatePattern (NULL);// 6
    CGContextSetFillColorSpace (myContext, patternSpace);// 7
    CGColorSpaceRelease (patternSpace);// 8

    pattern = CGPatternCreate (NULL,CGRectMake (0, 0, PATTERN_SIZE, PATTERN_SIZE),
    CGAffineTransformIdentity, PATTERN_SIZE, PATTERN_SIZE,
    kCGPatternTilingConstantSpacing true, &callbacks);

    CGContextSetFillPattern (myContext, pattern, &alpha);// 17
    CGPatternRelease (pattern);// 18
    //CGContextFillRect(myContext, rect);
    CGContextDrawPath(myContext, kCGPathFill);
    CGContextRestoreGState (myContext);

}

Here is a snippet of the code where I would like to call the routine:

CGContextSetLineWidth(context, .7);
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);


// Standard non-inverted view scenario.
CGContextBeginPath(context);

CGContextMoveToPoint(context, 0.00 , bMargin);      
CGContextAddLineToPoint(context, highPX - curveSP , bMargin);
[self addCurve:context startX:highPX startY:bMargin radius:bo curveSp:curveSP curveDir:FL_BL];

CGContextAddLineToPoint(context, highPX, ((h - tMargin) - curveSP) );
[self addCurve:context startX:highPX startY: (h - tMargin) radius:bo curveSp:curveSP curveDir:FL_TL];


CGContextAddLineToPoint(context, (lowPX - curveSP), (h - tMargin) );
[self addCurve:context startX: lowPX  startY: (h - tMargin) radius:bo curveSp:curveSP curveDir:FL_TR];

CGContextAddLineToPoint(context, lowPX, (bMargin + curveSP) );      
[self addCurve:context startX:lowPX startY: bMargin  radius:bo curveSp:curveSP curveDir:FL_BR];

CGContextAddLineToPoint(context, w, bMargin);

//CGContextDrawPath(context, nonInvertedView);

CGContextDrawPath(context, kCGPathStroke);
// fill with pattern
drawPattern(context);

The actual apple example also includes an NSRect arg in the drawing method, but since I don't want to fill a rect, I figured I could omit that. not sure though.

Thanks

sunkehappy
  • 8,970
  • 5
  • 44
  • 65
Miek
  • 1,127
  • 4
  • 20
  • 35

2 Answers2

1

CGContextDrawPath resets the current path. (They used to mention that somewhere, but I couldn't find it in a quick search.)

Save the graphics state before you stroke, then restore before you fill with the pattern.

(I assume you're specifically trying to get an outer stroke by stroking and then filling over half of it. If you want or can accept a centered stroke, kCGPathFillStroke will do the job with a single CGContextDrawPath call.)

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • Thanks for your input. Please review my self answer and comment. To answer your question, I am trying to maintain a simple black stroke on the path and use the pattern as a fill. Stroking the path has not been a problem, it's replacing a standard color fill with the pattern thats been the challenge. I have been using draw path in my calling code. – Miek Apr 29 '13 at 16:44
  • 1
    @Miek: `kCGPathFillStroke` will do that. It doesn't use the same color for both; it uses the stroke color/pattern for stroking and the fill color/pattern for filling. – Peter Hosey Apr 29 '13 at 16:59
  • Yes you are correct, and I have been doing that throughout my app. This is more of a setFill or setPattern issue. If you are trying to imply I could have put drawPath into the drawPattern method and had success, I'm sure I tried that and had no luck. – Miek Apr 29 '13 at 17:03
  • @Miek: No, I'm not trying to imply that at all. I'm explicitly saying that. If you start from the code in the question, remove the `kCGPathStroke` in the latter code, and change `drawPattern` to use `kCGPathFillStroke`, then it should work. (I tested this myself using a simple circle.) – Peter Hosey Apr 29 '13 at 17:36
  • Yep. You're absolutely right. I was trying to use the drawPattern as a fill method when I should been using it as a draw method. Now I can implement it with correctness. Thanks a lot! – Miek Apr 29 '13 at 21:19
0

So heres an update: I don't fully understand whats happening, but if I drop the code in the drawPattern method into a test app with an empty rect, it draws just like it should. If I drop the code into my method for drawing a path into a view , I get very strange behavior; it even tries to redraw parts of the view controller it should't even know about.

As soon as I deleted the CGContextSaveGState() , CGColorSpaceRelease(), CGPatternRelease(), and the CGContextRestoreGState(), the code started doing exactly what I wanted. I modified the method to this:

static void drawPattern(CGContextRef *pContext){

    static CGPatternRef    pattern;
    static CGColorSpaceRef patternSpace;
    static CGFloat         alpha = 1.0;


static const CGPatternCallbacks callbacks = {0, &patternSpec, NULL};

    patternSpace = CGColorSpaceCreatePattern (NULL);
    CGContextSetFillColorSpace (pContext, patternSpace);

    pattern = CGPatternCreate (NULL, 
               CGRectMake (0, 0, PATTERN_SIZE, PATTERN_SIZE),
               CGAffineTransformIdentity,
               PATTERN_SIZE, 
               PATTERN_SIZE, 
               kCGPatternTilingConstantSpacing,
               true, &callbacks);


     CGContextSetFillPattern (pContext, pattern, &alpha);   

}

Now I can either call the defined pattern, or set a define fill color:

CGContextSetFillColor(context);
or:    
drawPattern(context);

I would appreciate input on this, because I would like to know if leaving out some of these saveState or Release methods is a problem such as memory leaks.

Thanks

Miek
  • 1,127
  • 4
  • 20
  • 35
  • Yes, not releasing things that you own is how you leak them. Saving and restoring the gstate isn't a memory issue; it's a correctness issue. I don't think it's required to gsave and grestore around the entire contents of a pattern callback. – Peter Hosey Apr 29 '13 at 16:59