I need to draw some shapes on retina display, so I convert logic points for retina display like this:
self.inverseScale = (CGFloat)1.0/[UIScreen mainScreen].scale;
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, inverseScale, inverseScale);
and after that define next:
CGContextSetShouldAntialias(context, NO);
CGContextSetLineWidth(context, 1.0);
Ok, now I get 640×960 retina display size, but I little confused about the coordinates.
I thought that if I need to draw some rectangle frame I need to do the next:
CGContextSetFillColorWithColor(context, someBlackColor);
CGContextFillRect(context, displaySizeRect);
CGContextSetStrokeColorWithColor(context, white);
CGContextAddRect(context, CGRectMake(0, 0, 639, 959));
CGContextStrokePath(context);
But instead of it, I have found that I need to write:
CGContextAddRect(context, CGRectMake(0, 1, 639, 959));
instead of:
CGContextAddRect(context, CGRectMake(0, 0, 639, 959));
Why???
Is my conversion is wrong or what?
EDITS/ADDITIONS AT BOTTOM
It sounds like you might be confused about how the coordinate system maps to the pixel grid. When you’re drawing into a CGContext, you’re drawing into a “continuous” floating-point-based plane. This continuous plane is mapped onto the pixel-grid of the display such that integer values fall on the lines between screen pixels.
In theory, at default scale for any given device (so 1.0 for non-retina, 2.0 for retina), if you drew a rect from 0,0 -> 320,480, with a 100% opacity black stroke with a width of 1.0pt, you could expect the following results:
of the screen with 50% opacity.
wide rect around the outside of the screen with 100% opacity.
This stems from the fact that the zero line is right at the edge of the display, between the first row/col of pixels in the display and the first row/col pixels NOT in the display. When you draw a 1pt line at default scale in that situation, it draws along the center of the path, thus half of it will be drawn off the display, and in your continuous plane, you’ll have a line whose width extends from -0.5pt to 0.5pt. On a non-retina display, that will become a 1px line at 50% opacity, and on retina display that will be rendered as a 1px line with 100% opacity.
EDITS
OP said:
There is nothing about this goal that requires antialiasing to be turned off. If you are seeing alpha blending on horizontal and vertical lines with antialiasing turned on, then you are not drawing the lines in the right places or at the right sizes.
The reason you are seeing this confusing behavior is that you have antialiasing turned off. The key being able to see and understand what’s going on here is to leave antialiasing on, and then make the necessary changes until you get it just right. The easiest way to get started doing this is to leave the CGContext’s transform unchanged from the default and change the values you’re passing to the draw routines. It’s true that you can also do some of this work by transforming the CGContext, but that adds a step of math that’s done on every coordinate you pass in to any CG routine, which you can’t step into in the debugger, so I highly recommend that you start with the standard transform and with AA left on. You will want to develop a full understanding of how CG drawing works before attempting to mess with the context transform.
In a word, “no,” because CGContexts are not universally bitmaps (for instance, you could be drawing into a PDF — you can’t know from asking the CGContext). The plane you’re drawing into is intrinsically a floating-point plane. That’s just how it works. It is completely possible to achieve 100% pixel-accurate bitmap drawing using the floating point plane, but in order to do so, you have to understand how this stuff actually works.
You might be able to get bootstrapped faster by taking the default CGContext that you’re given and making this call:
What this will do is add (0.5, 0.5) to every point you ever pass in to all subsequent drawing calls (until you call CGContextRestoreGState). If you make no other changes to the context, that should make it such that when you draw a line from 0,0 -> 10,0 it will, even with antialiasing on, be perfectly pixel aligned. (See my initial answer above to understand why this is the case.)
Using this 0.5, 0.5 trick may get you started faster, but if you want to work with pixel-accurate CG drawing, you really need to get your head around how floating-point-based graphics contexts work, and how they relate to the bitmaps that may (or may not) back them.
Turning AA off and then nudging values around until they’re “right” is just asking for trouble later. For instance, say some day UIKit passes you a context that has a different flip in it’s transform? In that case, one of your values might round down, where it used to round up (because now it’s being multiplied by -1.0 when the flip is applied). The same problem can happen with contexts that have been translated into a negative quadrant. Furthermore, you don’t know (and can’t strictly rely on, version to version) what rounding rule CoreGraphics is going to use when you turn AA off, so if you end up being handed contexts with different CTMs for whatever reason you’ll get inconsistent results.
For the record, the time when you might want to turn antialiasing off would be when you’re drawing non-rectilinear paths and you don’t want CoreGraphics to alpha blend the edges. You may indeed want that effect, but turning off AA makes it much harder to learn, understand and be sure of what’s going on, so I highly recommend leaving it on until you fully understand this stuff, and have gotten all your rectilinear drawing perfectly pixel aligned. Then flip the switch to get rid of AA on non-rectilinear paths.
Turning off antialiasing does NOT magically allow a CGContext to be addressed as a bitmap. It takes a floating point plane and adds a rounding step that you can’t see (code-wise), can’t control, and which is hard to understand and predict. In the end, you still have a floating point plane.
Just a quick example to help clarify:
Make a new single-view application, add a custom view subclass with this drawRect: method, and set the default view in the .xib file to use your custom class. Both sides of this if statement produce the same results on retina display: a 100×100 device-pixel, non-alpha-blended square. The first side does it by using the “right” values for the default scale. The else side of it does it by backing out the 2x scale, and then translating the plane from being aligned to the edges of device pixels to being aligned with the center of device pixels. Note how the stroke widths are different (scale factors apply to them too.) Hope this helps.
OP replied:
No, really. Trust me. There’s a reason for this, and it’s NOT anti-aliasing being turned on in the context. (Also, I think you mean 3200% zoom, not 3200x zoom — at 3200x zoom a single pixel wouldn’t fit on a 30″ display.)
In the example I gave, we were drawing a rect, so we didn’t need to think about line endings, since it’s a closed path — the line is continuous. Now that you’re drawing a single segment, you do have to think about line endings to avoid alpha blending. This is the “edge of the pixel” vs. “center of the pixel” thing coming back around. The default line cap style is kCGLineCapButt. kCGLineCapButt means that the end of the line starts exactly where you start drawing. If you want it to behave more like pen — that is to say, if you put a felt-tip pen down, intending to draw a line 10 units to the right, some amount of ink is going to bleed out to the left of the exact point you pointed the pen at — you might consider using kCGLineCapSquare (or kCGLineCapRound for a rounded end, but for single-pixel-level drawing that will just drive you mad with alpha-blending, since it will calculate the alpha as 1.0 – 0.5/pi). I’ve overlaid hypothetical pixel grids on this illustration from Apple’s Quartz 2D Programming Guide to illustrate how line endings relate to pixels:
But, I digress. Here’s an example; Consider the following code:
Notice here that instead of moving to 2.25, 2.25, I move to 2.0, 2.25. This means I’m at the edge of the pixel in the X dimension, and the center of the pixel in the Y dimension. Therefore, I wont get alpha blending at the ends of the line. Indeed, zoomed to 3200% in Acorn, I see the following:
Now, yes, at some point, way beyond the point of caring (for most folks) you may run into accumulated floating point error if you’re transforming values or working with a long pipeline of calculations. I’ve seen error as significant as 0.000001 creep up in very complex situations, and even an error like that can bite you if you’re talking about situations like the difference between 1.999999 vs 2.000001, but that is NOT what you’re seeing here.
If your goal is only to draw pixel accurate bitmaps, consisting of axis-aligned/rectilinear elements only, there is NO reason to turn off context anti-aliasing. At the zoom levels and bitmap densities we’re talking about in this situation, you should easily remain free of almost all problems caused by 1e-6 or smaller magnitude floating point errors. (In fact in this case, anything smaller than 1/256th of one pixel will not have any effect on alpha blending in an 8-bit context, since such error will effectively be zero when quantized to 8 bits.)
Let me take this opportunity to recommend a book: Programming with Quartz: 2D and PDF Graphics in Mac OS X It’s a great read, and covers all this sort of stuff in great detail.