0

I am trying to use CGContextClip() to do some drawing, however I am running into some weird antialiasing problems. The problem appears to be that that the filling is being blended with the colour that it is replacing, instead of the surrounding pixels. See images for a slightly better description of the problem!

Example of issue:

enter image description here

(Note that the blue edge is being antialiased correctly)

Weird antialiased edge:

enter image description here (Edge should be blended with white, not blue)

Code:

-(void) drawRect:(CGRect)rect {

    CGContextRef c = UIGraphicsGetCurrentContext();

    CGContextSaveGState(c);
    {

        CGContextBeginPath(c);
        CGContextAddEllipseInRect(c, CGRectMake(25, 25, 200, 200));
        CGContextClosePath(c);
        CGContextClip(c);

        CGContextSetFillColorWithColor(c, [UIColor blueColor].CGColor);
        CGContextFillRect(c, rect);

        CGContextSetFillColorWithColor(c, [UIColor redColor].CGColor);
        CGContextFillRect(c, CGRectMake(0, 0, 250, 125));

    }
    CGContextRestoreGState(c);


}

Any ideas on how to prevent this weird antialiasing? I can't disable it, as the circle edges still need antialiasing!

Just to be clear, this is simply an example. I am trying to solve this issue on a much more complicated shape, where the solution of only filling half of the clipping to begin with simply isn't possible!

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Try filling only half the rect with blue. There is blue under the red, so what CG is doing is correct... – QED Nov 14 '14 at 21:22
  • That would probably resolve the issue in this case, however it's not a solution I can apply to anything else! With more complex shapes, this simply isn't possible. Sorry, I should have made clear in the OP that I am trying to solve this for a more complicated issue, I am only demonstrating it with this example for simplicity. I will edit the OP. – Hamish Nov 14 '14 at 21:25
  • Also, it's definitely not correct behaviour! It would make sense if the red fill didn't fully cover the blue background (i.e if there was a 1px blue edge around the circle), but it does. If I turn antialiasing off, the red color occupies the entire top half of the circle – Hamish Nov 14 '14 at 21:29
  • Does setting the blend mode help? For example, `kCGBlendModeCopy`. – Ken Thomases Nov 14 '14 at 21:36
  • Because antialiasing is on, the red pixels at the edge get assigned an alpha by CG. The blue pixels that are there also are assigned an alpha. The blue pixels are drawn first, so they are "underneath" and showing through the semi-transparent red. – QED Nov 14 '14 at 21:36
  • Why is the red treated as semi-transparent? It has an alpha of 1. Surely it should be treated as opaque? – Hamish Nov 14 '14 at 21:39
  • @KenThomases unfortunately, no it produces the same result :( – Hamish Nov 14 '14 at 21:43
  • 1
    Because that's how antialiasing works. – QED Nov 14 '14 at 21:44
  • You could draw to a `CGLayer` without the overall clipping to the circle, then draw the `CGLayer` to the context with the circle clipping. – Ken Thomases Nov 14 '14 at 21:47
  • @KenThomases That could potentially work.. however after doing some research it looks like CGLayer is pretty discouraged as it's a [very old and unloved tool](http://iosptl.com/posts/cglayer-no-longer-recommended/), it also seems a little expensive for what I am trying to achieve, seeing as I will be having to use it every time for each shape I want to render! I did however find a semi-solution which I have posted as an answer below. – Hamish Nov 15 '14 at 21:53

1 Answers1

0

Well, this doesn't 100% fix the problem, but it does reduce the weird antialiasing by a considerable amount. I got the idea from this question. The idea is that you clear any previous rendering in the area where you are going to draw before drawing, by changing the blend mode to clear, performing the drawing and then switching it back to normal and then re-drawing. The modified code looks like this:

-(void) drawRect:(CGRect)rect {

    CGContextRef c = UIGraphicsGetCurrentContext();

    CGContextSaveGState(c);
    {

        CGContextBeginPath(c);
        CGContextAddEllipseInRect(c, CGRectMake(25, 25, 200, 200));
        CGContextClosePath(c);
        CGContextClip(c);

        CGContextSetFillColorWithColor(c, [UIColor blueColor].CGColor);
        CGContextFillRect(c, CGRectMake(25, 25, 200, 200));

        CGContextSetFillColorWithColor(c, [UIColor redColor].CGColor);
        CGRect r = CGRectMake(0, 0, 250, 125);

        CGContextSetBlendMode(c, kCGBlendModeClear);
        CGContextFillRect(c, r);
        CGContextSetBlendMode(c, kCGBlendModeNormal);
        CGContextFillRect(c, r);


    }
    CGContextRestoreGState(c);

} 

If anyone comes up with a better solution that 100% fixes this issue, then post it as an answer and I'll accept it!

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • I see this is pretty old, so you've probably long moved on. I'm a novice fooling around with this stuff, but I notice your code doesn't do anything about stroke color or line width. Is it possible just setting those (either color to match the fill, or width to 0.0) will solve this? – user1272965 Jul 29 '17 at 15:01