UIBezierPath stroke 1px line and fill 1px width rectangle - different results.

Michael picture Michael · Jun 24, 2012 · Viewed 21.4k times · Source

Here is a simple drawing

    

- (void)drawRect:(CGRect)rect
{
    //vertical line with 1 px stroking
    UIBezierPath *vertLine = [[UIBezierPath alloc] init];
    [vertLine moveToPoint:CGPointMake(20.0, 10.0)];
    [vertLine addLineToPoint:CGPointMake(20.0, 400.0)];
    vertLine.lineWidth = 1.0;
    [[UIColor blackColor] setStroke];
    [vertLine stroke];

    //vertical rectangle 1px width 
    UIBezierPath *vertRect= [UIBezierPath bezierPathWithRect:CGRectMake(40.0, 10.0, 1.0, 390.0)];
    [[UIColor blackColor] setFill];
    [vertRect fill];

}

On non retina 3GS and simulator the first line is blurry and looks wider than 1 px, but the second line is crisp.

Unfortunately I have neither iPhone4 nor the new iPad to test, but on retina simulator both lines look the same.

Question: Is rectangle instead of stroke the only way to obtain the same result for non retina and retina devices?

Answer

David Rönnqvist picture David Rönnqvist · Jun 24, 2012

You are filling the inside of the rectangle but you are stroking the line from the center. Since the coordinates in both cases (the corners of the rectangle and the start and end coordinates in the line) are defined as whole number values (no fractions), the coordinates lie on exact point boundaries.

I said "coordinates" above when talking about the points of the line, to not confuse them with the points on the screen. I also said "point boundaries" instead of "pixel boundaries" for the same reason. iOS defines its coordinates and all the points in what is called "points" instead of pixels. A point is a resolution independent measurement. Both the retina and non-retina devices have the same number of points on the screen, it's just that they correspond to a different number of actual pixels.

Let's look at stroking a line that lie on the point boundaries (like in your question) compared to filling a rectangle where the corners lie on the the point boundaries:

In the below illustrations I am stroking a line with black and filling a rectangle with orange on both a non-retina screen and a retina screen. I've also outlines the line and the rectangle with blue. In both cases you can see the size of a point for that resolution and compare it to the actual pixel grid.

In the non-retina case, you can see that trying to stroke the line from the center with a 1 point line with (in this case corresponding to a 1 pixel line width) would fill half of the pixels on top and half on the pixels below. Since the pixels are only half filled, the opacity for those pixels are 50%. This results in the lighter color (on a white background). Since both the pixels on top and below are party filled, stroking the fills both the pixels on top and below. This makes the line look as if it's 2 pixels wide instead of one.

You can quickly compare that to the rectangle which is filled on the inside.

non retina

The same case on a retina screen looks different. In this case, the size of a point is the same but it consists of 4 pixels instead of 1. This time, when stroking the line, half a point above the line and half a point below the line will fully fill the row of pixels above and below because of the higher resolution screen. This means that the line looks as if it's 1 point wide and that the color looks fully opaque.

We can also see that the filled rectangle looks the same.

retina


To fix this, you would put the points for your line on half pixels. Stroking the line from the center on a low resolution device means that the line extends half a point upwards and half a point downwards. Since the center of the line now lies in the center of the point, this means that the stroked line fully lies within the pixels and the line looks sharp. Doing this won't have any effect on the retina line since moving down (or up) half a point, still means that you fully fill the pixels above and below.

In the illustration below (for retina) I have shown both the point grid and the pixel grid.

half pixel non-retina half pixel retina